Quellcode durchsuchen

Promoting Zend_Cloud to trunk

- Merged Zend_Cloud (SimpleCloud API) from laboratory to trunk. Includes:
  - documentation
  - tests
  - library code
  - "Cloud Explorer" demo

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@23004 44c647ce-9c0f-0410-b52a-842ac1e357ba
matthew vor 15 Jahren
Ursprung
Commit
5f1c1b55c3
100 geänderte Dateien mit 13649 neuen und 1 gelöschten Zeilen
  1. 126 0
      demos/Zend/Cloud/cloudexp/.zfproject.xml
  2. 25 0
      demos/Zend/Cloud/cloudexp/README.txt
  3. 42 0
      demos/Zend/Cloud/cloudexp/application/Bootstrap.php
  4. 32 0
      demos/Zend/Cloud/cloudexp/application/configs/application.ini.dist
  5. 120 0
      demos/Zend/Cloud/cloudexp/application/controllers/DocumentController.php
  6. 73 0
      demos/Zend/Cloud/cloudexp/application/controllers/ErrorController.php
  7. 35 0
      demos/Zend/Cloud/cloudexp/application/controllers/IndexController.php
  8. 96 0
      demos/Zend/Cloud/cloudexp/application/controllers/QueueController.php
  9. 117 0
      demos/Zend/Cloud/cloudexp/application/controllers/StorageController.php
  10. 22 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/document/add-document.phtml
  11. 4 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/document/create.phtml
  12. 1 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/document/delete-document.phtml
  13. 7 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/document/index.phtml
  14. 27 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/document/show.phtml
  15. 28 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/error/error.phtml
  16. 17 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/index/index.phtml
  17. 4 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/queue/create.phtml
  18. 18 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/queue/index.phtml
  19. 8 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/queue/receive.phtml
  20. 17 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/queue/send.phtml
  21. 1 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/storage/get.phtml
  22. 3 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/storage/index.phtml
  23. 6 0
      demos/Zend/Cloud/cloudexp/application/views/scripts/storage/upload.phtml
  24. 53 0
      demos/Zend/Cloud/cloudexp/library/CloudExplorer/ResourceInjector.php
  25. 7 0
      demos/Zend/Cloud/cloudexp/public/.htaccess
  26. 26 0
      demos/Zend/Cloud/cloudexp/public/index.php
  27. 19 0
      documentation/manual/en/manual.xml.in
  28. 984 0
      documentation/manual/en/module_specs/Zend_Cloud_DocumentService.xml
  29. 536 0
      documentation/manual/en/module_specs/Zend_Cloud_QueueService.xml
  30. 717 0
      documentation/manual/en/module_specs/Zend_Cloud_StorageService.xml
  31. 67 0
      library/Zend/Cloud/AbstractFactory.php
  32. 155 0
      library/Zend/Cloud/DocumentService/Adapter.php
  33. 130 0
      library/Zend/Cloud/DocumentService/Adapter/AbstractAdapter.php
  34. 468 0
      library/Zend/Cloud/DocumentService/Adapter/SimpleDb.php
  35. 175 0
      library/Zend/Cloud/DocumentService/Adapter/SimpleDb/Query.php
  36. 628 0
      library/Zend/Cloud/DocumentService/Adapter/WindowsAzure.php
  37. 171 0
      library/Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php
  38. 248 0
      library/Zend/Cloud/DocumentService/Document.php
  39. 68 0
      library/Zend/Cloud/DocumentService/DocumentSet.php
  40. 38 0
      library/Zend/Cloud/DocumentService/Exception.php
  41. 77 0
      library/Zend/Cloud/DocumentService/Factory.php
  42. 191 0
      library/Zend/Cloud/DocumentService/Query.php
  43. 102 0
      library/Zend/Cloud/DocumentService/QueryAdapter.php
  44. 53 0
      library/Zend/Cloud/Exception.php
  45. 34 0
      library/Zend/Cloud/OperationNotAvailableException.php
  46. 146 0
      library/Zend/Cloud/QueueService/Adapter.php
  47. 92 0
      library/Zend/Cloud/QueueService/Adapter/AbstractAdapter.php
  48. 278 0
      library/Zend/Cloud/QueueService/Adapter/Sqs.php
  49. 343 0
      library/Zend/Cloud/QueueService/Adapter/WindowsAzure.php
  50. 301 0
      library/Zend/Cloud/QueueService/Adapter/ZendQueue.php
  51. 37 0
      library/Zend/Cloud/QueueService/Exception.php
  52. 71 0
      library/Zend/Cloud/QueueService/Factory.php
  53. 60 0
      library/Zend/Cloud/QueueService/Message.php
  54. 68 0
      library/Zend/Cloud/QueueService/MessageSet.php
  55. 145 0
      library/Zend/Cloud/StorageService/Adapter.php
  56. 267 0
      library/Zend/Cloud/StorageService/Adapter/FileSystem.php
  57. 399 0
      library/Zend/Cloud/StorageService/Adapter/Nirvanix.php
  58. 327 0
      library/Zend/Cloud/StorageService/Adapter/S3.php
  59. 443 0
      library/Zend/Cloud/StorageService/Adapter/WindowsAzure.php
  60. 38 0
      library/Zend/Cloud/StorageService/Exception.php
  61. 70 0
      library/Zend/Cloud/StorageService/Factory.php
  62. 82 0
      library/Zend/Service/Amazon/Authentication.php
  63. 37 0
      library/Zend/Service/Amazon/Authentication/Exception.php
  64. 112 0
      library/Zend/Service/Amazon/Authentication/S3.php
  65. 108 0
      library/Zend/Service/Amazon/Authentication/V1.php
  66. 138 0
      library/Zend/Service/Amazon/Authentication/V2.php
  67. 578 0
      library/Zend/Service/Amazon/SimpleDb.php
  68. 100 0
      library/Zend/Service/Amazon/SimpleDb/Attribute.php
  69. 66 0
      library/Zend/Service/Amazon/SimpleDb/Exception.php
  70. 97 0
      library/Zend/Service/Amazon/SimpleDb/Page.php
  71. 190 0
      library/Zend/Service/Amazon/SimpleDb/Response.php
  72. 28 1
      tests/TestConfiguration.php.dist
  73. 2 0
      tests/Zend/AllTests.php
  74. 72 0
      tests/Zend/Cloud/AllTests.php
  75. 79 0
      tests/Zend/Cloud/DocumentService/Adapter/AllTests.php
  76. 198 0
      tests/Zend/Cloud/DocumentService/Adapter/SimpleDbTest.php
  77. 164 0
      tests/Zend/Cloud/DocumentService/Adapter/WindowsAzureTest.php
  78. 61 0
      tests/Zend/Cloud/DocumentService/AllTests.php
  79. 89 0
      tests/Zend/Cloud/DocumentService/FactoryTest.php
  80. 395 0
      tests/Zend/Cloud/DocumentService/TestCase.php
  81. 84 0
      tests/Zend/Cloud/QueueService/Adapter/AllTests.php
  82. 155 0
      tests/Zend/Cloud/QueueService/Adapter/SqsTest.php
  83. 105 0
      tests/Zend/Cloud/QueueService/Adapter/WindowsAzureTest.php
  84. 89 0
      tests/Zend/Cloud/QueueService/Adapter/ZendQueueTest.php
  85. 63 0
      tests/Zend/Cloud/QueueService/AllTests.php
  86. 103 0
      tests/Zend/Cloud/QueueService/FactoryTest.php
  87. 352 0
      tests/Zend/Cloud/QueueService/TestCase.php
  88. 3 0
      tests/Zend/Cloud/QueueService/_files/config/sqs.ini
  89. 8 0
      tests/Zend/Cloud/QueueService/_files/config/windowsazure.ini
  90. 2 0
      tests/Zend/Cloud/QueueService/_files/config/zendqueue.ini
  91. 91 0
      tests/Zend/Cloud/StorageService/Adapter/AllTests.php
  92. 155 0
      tests/Zend/Cloud/StorageService/Adapter/FileSystemTest.php
  93. 110 0
      tests/Zend/Cloud/StorageService/Adapter/NirvanixTest.php
  94. 145 0
      tests/Zend/Cloud/StorageService/Adapter/S3Test.php
  95. 90 0
      tests/Zend/Cloud/StorageService/Adapter/WindowsAzureTest.php
  96. 63 0
      tests/Zend/Cloud/StorageService/AllTests.php
  97. 156 0
      tests/Zend/Cloud/StorageService/FactoryTest.php
  98. 411 0
      tests/Zend/Cloud/StorageService/TestCase.php
  99. 2 0
      tests/Zend/Cloud/StorageService/_files/config/filesystem.ini
  100. 5 0
      tests/Zend/Cloud/StorageService/_files/config/nirvanix.ini

+ 126 - 0
demos/Zend/Cloud/cloudexp/.zfproject.xml

@@ -0,0 +1,126 @@
+<?xml version="1.0"?>
+<projectProfile type="default" version="1.10">
+  <projectDirectory>
+    <projectProfileFile filesystemName=".zfproject.xml"/>
+    <applicationDirectory classNamePrefix="Application_">
+      <apisDirectory enabled="false"/>
+      <configsDirectory>
+        <applicationConfigFile type="ini"/>
+      </configsDirectory>
+      <controllersDirectory>
+        <controllerFile controllerName="Index">
+          <actionMethod actionName="index"/>
+        </controllerFile>
+        <controllerFile controllerName="Error"/>
+        <controllerFile controllerName="Storage">
+          <actionMethod actionName="index"/>
+          <actionMethod actionName="get"/>
+          <actionMethod actionName="upload"/>
+        </controllerFile>
+        <controllerFile controllerName="Queue">
+          <actionMethod actionName="index"/>
+          <actionMethod actionName="create"/>
+          <actionMethod actionName="send"/>
+          <actionMethod actionName="receive"/>
+        </controllerFile>
+        <controllerFile controllerName="Document">
+          <actionMethod actionName="index"/>
+          <actionMethod actionName="show"/>
+          <actionMethod actionName="create"/>
+          <actionMethod actionName="addDocument"/>
+          <actionMethod actionName="deleteDocument"/>
+        </controllerFile>
+      </controllersDirectory>
+      <formsDirectory enabled="false"/>
+      <layoutsDirectory enabled="false"/>
+      <modelsDirectory/>
+      <modulesDirectory enabled="false"/>
+      <viewsDirectory>
+        <viewScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Index">
+            <viewScriptFile forActionName="index"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Error">
+            <viewScriptFile forActionName="error"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Storage">
+            <viewScriptFile forActionName="index"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Storage">
+            <viewScriptFile forActionName="get"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Storage">
+            <viewScriptFile forActionName="upload"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Queue">
+            <viewScriptFile forActionName="index"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Queue">
+            <viewScriptFile forActionName="create"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Queue">
+            <viewScriptFile forActionName="send"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Queue">
+            <viewScriptFile forActionName="receive"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Document">
+            <viewScriptFile forActionName="index"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Document">
+            <viewScriptFile forActionName="show"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Document">
+            <viewScriptFile forActionName="create"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Document">
+            <viewScriptFile forActionName="addDocument"/>
+          </viewControllerScriptsDirectory>
+          <viewControllerScriptsDirectory forControllerName="Document">
+            <viewScriptFile forActionName="deleteDocument"/>
+          </viewControllerScriptsDirectory>
+        </viewScriptsDirectory>
+        <viewHelpersDirectory/>
+        <viewFiltersDirectory enabled="false"/>
+      </viewsDirectory>
+      <bootstrapFile filesystemName="Bootstrap.php"/>
+    </applicationDirectory>
+    <dataDirectory enabled="false">
+      <cacheDirectory enabled="false"/>
+      <searchIndexesDirectory enabled="false"/>
+      <localesDirectory enabled="false"/>
+      <logsDirectory enabled="false"/>
+      <sessionsDirectory enabled="false"/>
+      <uploadsDirectory enabled="false"/>
+    </dataDirectory>
+    <docsDirectory>
+      <file filesystemName="README.txt"/>
+    </docsDirectory>
+    <libraryDirectory>
+      <zfStandardLibraryDirectory enabled="false"/>
+    </libraryDirectory>
+    <publicDirectory>
+      <publicStylesheetsDirectory enabled="false"/>
+      <publicScriptsDirectory enabled="false"/>
+      <publicImagesDirectory enabled="false"/>
+      <publicIndexFile filesystemName="index.php"/>
+      <htaccessFile filesystemName=".htaccess"/>
+    </publicDirectory>
+    <projectProvidersDirectory enabled="false"/>
+    <temporaryDirectory enabled="false"/>
+    <testsDirectory>
+      <testPHPUnitConfigFile filesystemName="phpunit.xml"/>
+      <testApplicationDirectory>
+        <testApplicationBootstrapFile filesystemName="bootstrap.php"/>
+        <testApplicationControllerDirectory>
+          <testApplicationControllerFile filesystemName="StorageControllerTest.php"/>
+          <testApplicationControllerFile filesystemName="QueueControllerTest.php"/>
+          <testApplicationControllerFile filesystemName="DocumentControllerTest.php"/>
+        </testApplicationControllerDirectory>
+      </testApplicationDirectory>
+      <testLibraryDirectory>
+        <testLibraryBootstrapFile filesystemName="bootstrap.php"/>
+      </testLibraryDirectory>
+    </testsDirectory>
+  </projectDirectory>
+</projectProfile>

+ 25 - 0
demos/Zend/Cloud/cloudexp/README.txt

@@ -0,0 +1,25 @@
+Cloud Explorer
+--------------
+
+Cloud Explorer is written as a demonstration of the Simple Cloud API as
+implemented in Zend Framework (Zend_Cloud component). It provides the
+ability:
+
+ * to browse collections within a document storage, and to add and
+   delete documents from collections
+ * to create queues, and to send and receive messages from queues
+ * to upload and retrieve files to and from a storage service
+
+To try it out:
+
+ * You will either need Zend Framework on your include_path, or you will
+   need to symlink it into the library/ subdirectory.
+ * You will need to create a virtual host pointing at the public/
+   subdirectory as the DocumentRoot.
+ * You will need to copy application/configs/application.ini.dist to
+   application/configs/application.ini, and edit it to point at the
+   appropriate services, and to provide the appropriate credentials for
+   those services.
+
+Once you have accomplished the above, simply fire up a browser and point
+it to your virtual host.

+ 42 - 0
demos/Zend/Cloud/cloudexp/application/Bootstrap.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
+{
+	protected function _initConfig()
+	{
+		return  new Zend_Config($this->getOptions());
+	}
+	
+	protected function _initResourceInjector()
+    {
+        Zend_Controller_Action_HelperBroker::addHelper(
+            new CloudExplorer_ResourceInjector()
+        );
+    }
+}

+ 32 - 0
demos/Zend/Cloud/cloudexp/application/configs/application.ini.dist

@@ -0,0 +1,32 @@
+[production]
+autoloadernamespaces[] = "CloudExplorer_"
+
+phpSettings.display_startup_errors = 0
+phpSettings.display_errors = 0
+includePaths.library = APPLICATION_PATH "/../library"
+bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
+bootstrap.class = "Bootstrap"
+appnamespace = "Application"
+resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
+resources.frontController.params.displayExceptions = 0
+
+storage.storage_adapter = "Zend_Cloud_StorageService_Adapter_S3"
+storage.bucket_name = cloudexp
+storage.aws_accesskey = TEST
+storage.aws_secretkey = TEST
+
+queue.queue_adapter = "Zend_Cloud_QueueService_Adapter_WindowsAzure"
+queue.storage_host = "queue.core.windows.net"
+queue.storage_accountname = TEST
+queue.storage_accountkey = TEST
+
+[staging : production]
+
+[testing : production]
+phpSettings.display_startup_errors = 1
+phpSettings.display_errors = 1
+
+[development : production]
+phpSettings.display_startup_errors = 1
+phpSettings.display_errors = 1
+resources.frontController.params.displayExceptions = 1

+ 120 - 0
demos/Zend/Cloud/cloudexp/application/controllers/DocumentController.php

@@ -0,0 +1,120 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class DocumentController extends Zend_Controller_Action
+{
+    
+    public $dependencies = array('config');
+    
+    /**
+     * @var Zend_Cloud_DocumentService_Adapter
+     */
+    protected $_doc = null;
+
+    public function preDispatch()
+    {
+        $this->_doc = Zend_Cloud_DocumentService_Factory::getAdapter(
+            $this->config->document
+        );
+    }
+
+    public function indexAction()
+    {
+        $this->view->collections = $this->_doc->listCollections();
+    }
+
+    public function showAction()
+    {
+        $request = $this->getRequest();
+        if (!$name = $this->view->collection = $this->_getParam('collection', false)) {
+            return;
+        }
+        $q = $this->_doc->select("*");
+        $this->view->data = $this->_doc->query($name, $q, array(
+            Zend_Cloud_DocumentService_Adapter_SimpleDB::RETURN_DOCUMENTS => true
+        ));
+    }
+
+    public function createAction()
+    {    
+    	$request = $this->getRequest();
+        if (!$request->isPost()) {
+            return;
+        }
+        if (!$name = $this->_getParam('name', false)) {
+            return;
+        }
+        $this->_doc->createCollection($name);
+        return $this->_helper->redirector('index');
+    }
+
+    public function addDocumentAction()
+    {
+    	$this->view->fieldcount = 5;
+    	$this->view->collections = $this->_doc->listCollections();
+    	$request = $this->getRequest();
+        if (!$request->isPost()) {
+            return;
+        }
+        if (!$name = $this->view->name =  $this->_getParam('name', false)) {
+            return;
+        }
+        if (!$id = $this->_getParam('id', false)) {
+            return;
+        }
+        $fields = array();
+        foreach ($this->_getParam('field', array()) as $field) {
+            if (!$field["name"]) {
+                continue;
+            }
+        	$fields[$field["name"]] = $field["value"];
+        }
+        if (empty($fields)) {
+        	return;
+        }
+        $document = new Zend_Cloud_DocumentService_Document($id, $fields);
+		$this->_doc->insertDocument($name, $document);
+        return $this->_helper->redirector('show', null, null, array("collection" => $name));
+    }
+
+    public function deleteDocumentAction()
+    {   
+    	$request = $this->getRequest();
+        if (!$request->isPost()) {
+            return;
+        }
+        if (!$name = $this->view->name =  $this->_getParam('name', false)) {
+            return;
+        }
+        if (!$id = $this->_getParam('id', false)) {
+            return;
+        }
+        $this->_doc->deleteDocument($name, $id);
+        return $this->_helper->redirector('show', null, null, array("collection" => $name));
+   }
+}

+ 73 - 0
demos/Zend/Cloud/cloudexp/application/controllers/ErrorController.php

@@ -0,0 +1,73 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class ErrorController extends Zend_Controller_Action
+{
+    public function errorAction()
+    {
+        $errors = $this->_getParam('error_handler');
+        
+        switch ($errors->type) {
+            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
+            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
+            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
+        
+                // 404 error -- controller or action not found
+                $this->getResponse()->setHttpResponseCode(404);
+                $this->view->message = 'Page not found';
+                break;
+            default:
+                // application error
+                $this->getResponse()->setHttpResponseCode(500);
+                $this->view->message = 'Application error';
+                break;
+        }
+        
+        // Log exception, if logger available
+        if ($log = $this->getLog()) {
+            $log->crit($this->view->message, $errors->exception);
+        }
+        
+        // conditionally display exceptions
+        if ($this->getInvokeArg('displayExceptions') == true) {
+            $this->view->exception = $errors->exception;
+        }
+        
+        $this->view->request   = $errors->request;
+    }
+
+    public function getLog()
+    {
+        $bootstrap = $this->getInvokeArg('bootstrap');
+        if (!$bootstrap->hasPluginResource('Log')) {
+            return false;
+        }
+        $log = $bootstrap->getResource('Log');
+        return $log;
+    }
+}

+ 35 - 0
demos/Zend/Cloud/cloudexp/application/controllers/IndexController.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class IndexController extends Zend_Controller_Action
+{
+    public function indexAction()
+    {
+        // action body
+    }
+}

+ 96 - 0
demos/Zend/Cloud/cloudexp/application/controllers/QueueController.php

@@ -0,0 +1,96 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class QueueController extends Zend_Controller_Action
+{
+    
+    public $dependencies = array('config');
+    
+    /**
+     * @var Zend_Cloud_QueueService_Adapter
+     */
+    protected $_queue = null;
+
+    public function preDispatch()
+    {
+        $this->_queue = Zend_Cloud_QueueService_Factory::getAdapter($this->config->queue);
+    }
+
+    public function indexAction()
+    {
+        $this->view->qs = $this->_queue->listQueues();
+    }
+
+    public function createAction()
+    {
+        $request = $this->getRequest();
+        if (!$request->isPost()) {
+            return;
+        }
+        if (!$name = $this->_getParam('name', false)) {
+            return;
+        }
+        $this->_queue->createQueue($name);
+        return $this->_helper->redirector('index');
+    }
+
+    public function sendAction()
+    {
+        $this->view->qs = $this->_queue->listQueues();
+    	$request        = $this->getRequest();
+        $name           = $this->view->name = $this->_getParam('name', false);
+     	if (!$name) {
+            return;
+        }
+        if (!$request->isPost()) {
+            return;
+        }
+        if (!$message = $this->_getParam('message', false)) {
+            return;
+        }
+        $ret = $this->_queue->sendMessage($name, $message);
+        return $this->_helper->redirector('index');
+    }
+
+    public function receiveAction()
+    {    
+        $this->view->qs = $this->_queue->listQueues();
+    	$request        = $this->getRequest();
+        $name           = $this->view->name = $this->_getParam('name', false);
+     	if (!$name) {
+            return;
+        }
+        $messages = $this->_queue->receiveMessages($name);
+        foreach ($messages as $msg) {
+        	$texts[] = $msg->getBody();
+        	// remove messages from the queue
+        	$this->_queue->deleteMessage($name, $msg);
+        }
+        $this->view->messages = $texts;
+    }
+}

+ 117 - 0
demos/Zend/Cloud/cloudexp/application/controllers/StorageController.php

@@ -0,0 +1,117 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class StorageController extends Zend_Controller_Action
+{
+    
+    public $dependencies = array('config');
+    
+    /**
+     * @var Zend_Cloud_StorageService_Adapter
+     */
+    protected $_storage = null;
+
+    public function preDispatch()
+    {
+        $this->_storage = Zend_Cloud_StorageService_Factory::getAdapter($this->config->storage);
+    }
+
+    public function indexAction()
+    {
+        $this->view->items = $this->_storage->listItems("/");
+    }
+
+    public function getAction()
+    {
+        if (!$name = $this->_getParam('item', false)) {
+            return $this->_helper->redirector('index');
+        }
+
+        $item = $this->_storage->fetchItem($name, array(
+        	Zend_Cloud_StorageService_Adapter_S3::FETCH_STREAM => true,
+        	Zend_Cloud_StorageService_Adapter_WindowsAzure::RETURN_TYPE => Zend_Cloud_StorageService_Adapter_WindowsAzure::RETURN_STREAM
+        ));
+
+        if (!$item) {
+            $this->getResponse()->setHttpResponseCode(404);
+            return;
+        }
+
+        $meta = $this->_storage->fetchMetadata($name);
+        if (isset($meta["type"])) {
+            $this->getResponse()->setHeader('Content-Type', $meta["type"]);
+        }
+
+        // don't render the view, send the item instead
+        $this->_helper->viewRenderer->setNoRender(true);
+        if ($item instanceof Zend_Http_Response_Stream) {
+            fpassthru($item->getStream());
+        } elseif (is_resource($item)) {
+            fpassthru($item);
+        } else {
+            $this->getResponse()->setBody($item);
+        }
+    }
+
+    public function uploadAction()
+    {
+    	$request = $this->getRequest();
+    	if (!$request->isPost()) {
+    		return;
+    	}
+    	$name = $this->_getParam('name', false);
+    	
+    	$upload = new Zend_File_Transfer();
+    	$upload->addValidator('Count', false, 1);
+	    if (!$upload->isValid()) {
+	    	return;
+		}
+		$upload->receive();
+    	$file = $upload->getFileName();
+		$fp   = fopen($file, "r");
+		if (!$fp) {
+			return;
+		}
+		$mime = $upload->getMimeType();
+		if (!$name) {
+			// get short name
+			$name = $upload->getFileName(null, false);
+		}
+
+        $this->_storage->storeItem($name, $fp, array(
+            Zend_Cloud_StorageService_Adapter_S3::METADATA => array("type" => $mime)
+        ));
+		try {
+			$this->_storage->storeMetadata($name, array("type" => $mime));
+		} catch(Zend_Cloud_OperationNotAvailableException $e) {
+			// ignore it
+		}
+
+		return $this->_helper->redirector('index');
+	}
+}

+ 22 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/document/add-document.phtml

@@ -0,0 +1,22 @@
+<form method="POST" enctype="multipart/form-data">
+Collection name: <select name="name">
+<?php 
+foreach($this->collections as $name) {
+	if($name == $this->name) {
+		$checked = " selected";
+	} else {
+		$checked = "";
+	}
+	echo "<option$checked>$name</option>";
+}
+?>
+</select>
+<br/>
+ID: <input type="text" name="id"/><br/>
+Fields:<br/> 
+<?php for ($i = 0; $i < $this->fieldcount; $i++): ?>
+<input type="text" name="field[<?php echo $i ?>][name]"/>: <input type="text" 
+    name="field[<?php echo $i ?>][value]"/><br/>
+<?php endfor ?>
+<input type="submit" value="Add!"/>
+</form>

+ 4 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/document/create.phtml

@@ -0,0 +1,4 @@
+<form method="POST" enctype="multipart/form-data">
+Queue name: <input type="text" name="name" /><br/>
+<input type="submit" value="Create!"/>
+</form>

+ 1 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/document/delete-document.phtml

@@ -0,0 +1 @@
+Invalid parameters specified.

+ 7 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/document/index.phtml

@@ -0,0 +1,7 @@
+<?php if (count($this->collections) == 0) {
+	echo "No collections.";
+	return;
+}
+foreach($this->collections as $coll): ?>
+<a href="<?php echo $this->url(array("action" => "show", "controller" => "document", "collection" => $coll)) ?>"><?php echo $coll ?></a><br/>
+<?php endforeach ?>

+ 27 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/document/show.phtml

@@ -0,0 +1,27 @@
+<?php 
+if (count($this->data) == 0):
+	echo "Collection is empty.<br/>";
+else:
+    foreach ($this->data as $document):
+?>
+Document ID: <?php echo $document->getID() ?><br/>
+Document data: <br/>
+<?php 
+    foreach($document->getFields() as $key => $value) { 
+        echo "<b>$key</b>: $value<br/>\n";
+    }
+?>
+<form method="POST" action="<?php echo $this->url(array("action" => "delete-document", "controller" => "document"), "", true) ?>" style="display: inline">
+<input type="hidden" name="name" value="<?php echo $this->collection ?>">
+<input type="hidden" name="id" value="<?php echo $document->getID() ?>">
+<input type="submit" value="Delete" style="display: inline">
+</form> <hr>
+<?php 
+    endforeach;
+endif;
+?>
+
+<form method="POST" action="<?php echo $this->url(array("action" => "add-document", "controller" => "document"), "", true) ?>" style="display: inline">
+<input type="hidden" name="name" value="<?php echo $this->collection ?>">
+<input type="submit" value="Add Document" style="display: inline">
+</form>

+ 28 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/error/error.phtml

@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <title>Zend Framework Default Application</title>
+</head>
+<body>
+  <h1>An error occurred</h1>
+  <h2><?php echo $this->message ?></h2>
+
+  <?php if (isset($this->exception)): ?>
+
+  <h3>Exception information:</h3>
+  <p>
+      <b>Message:</b> <?php echo $this->exception->getMessage() ?>
+  </p>
+
+  <h3>Stack trace:</h3>
+  <pre><?php echo $this->exception->getTraceAsString() ?>
+  </pre>
+
+  <h3>Request Parameters:</h3>
+  <pre><?php echo var_export($this->request->getParams(), true) ?>
+  </pre>
+  <?php endif ?>
+
+</body>
+</html>

+ 17 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/index/index.phtml

@@ -0,0 +1,17 @@
+<h1>Storage API</h1>
+
+<a href="<?php echo $this->url(array("action" => "index", "controller" => "storage")) ?>">List items</a><br/>
+<a href="<?php echo $this->url(array("action" => "upload", "controller" => "storage")) ?>">Upload item</a><br/>
+
+<h1>Queue API</h1>
+
+<a href="<?php echo $this->url(array("action" => "index", "controller" => "queue")) ?>">List queues</a><br/>
+<a href="<?php echo $this->url(array("action" => "create", "controller" => "queue")) ?>">Create queue</a><br/>
+<a href="<?php echo $this->url(array("action" => "send", "controller" => "queue")) ?>">Send messages</a><br/>
+<a href="<?php echo $this->url(array("action" => "receive", "controller" => "queue")) ?>">Receive messages</a><br/>
+
+<h1>Document API</h1>
+
+<a href="<?php echo $this->url(array("action" => "index", "controller" => "document")) ?>">List collections</a><br/>
+<a href="<?php echo $this->url(array("action" => "create", "controller" => "document")) ?>">Create collection</a><br/>
+

+ 4 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/queue/create.phtml

@@ -0,0 +1,4 @@
+<form method="POST" enctype="multipart/form-data">
+Queue name: <input type="text" name="name" /><br/>
+<input type="submit" value="Create!"/>
+</form>

+ 18 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/queue/index.phtml

@@ -0,0 +1,18 @@
+<?php 
+if (count($this->qs) == 0) {
+	echo "No queues.";
+	return;
+}
+?>
+<?php foreach ($this->qs as $queue): ?>
+<form method="POST" action="<?php echo $this->url(array("action" => "send", "controller" => "queue")) ?>" style="display: inline">
+Queue <?php echo $queue ?>: 
+<input type="hidden" name="name" value="<?php echo $queue ?>">
+<input type="submit" value="Send" style="display: inline">
+</form> 
+<form method="POST" action="<?php echo $this->url(array("action" => "receive", "controller" => "queue")) ?>" style="display: inline">
+<input type="hidden" name="name" value="<?php echo $queue ?>">
+<input type="submit" value="Receive" style="display: inline">
+</form> 
+<br/>
+<?php endforeach; ?>

+ 8 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/queue/receive.phtml

@@ -0,0 +1,8 @@
+<?php
+if (count($this->messages) == 0) {
+	echo "No messages.";
+	return;
+}
+foreach ($this->messages as $message) {
+	echo "Message from $this->name: ".$message;
+} 

+ 17 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/queue/send.phtml

@@ -0,0 +1,17 @@
+<form method="POST" enctype="multipart/form-data">
+Queue name: <select name="name">
+<?php 
+foreach ($this->qs as $name) {
+	if ($name == $this->name) {
+		$checked = " selected";
+	} else {
+		$checked = "";
+	}
+	echo "<option$checked>$name</option>";
+}
+?>
+</select>
+<br/>
+Message: <input type="text" name="message"/><br/> 
+<input type="submit" value="Send!"/>
+</form>

+ 1 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/storage/get.phtml

@@ -0,0 +1 @@
+The item was not found, sorry.

+ 3 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/storage/index.phtml

@@ -0,0 +1,3 @@
+<?php foreach ($this->items as $item): ?>
+<a href="<?php echo $this->url(array("action" => "get", "controller" => "storage", "item" => $item)) ?>"><?php echo $item ?></a><br/>
+<?php endforeach; ?>

+ 6 - 0
demos/Zend/Cloud/cloudexp/application/views/scripts/storage/upload.phtml

@@ -0,0 +1,6 @@
+<form method="POST" enctype="multipart/form-data">
+<input type="hidden" name="MAX_FILE_SIZE"  value="1000000" />
+Object name: <input type="text" name="name" /><br/>
+Choose a file to upload:  <input name="uploadfile" type="file" /><br/>
+<input type="submit" value="Upload!"/>
+</form>

+ 53 - 0
demos/Zend/Cloud/cloudexp/library/CloudExplorer/ResourceInjector.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage examples
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class CloudExplorer_ResourceInjector extends Zend_Controller_Action_Helper_Abstract
+{
+    public function preDispatch()
+    {
+    	$bootstrap  = $this->getBootstrap();
+        $controller = $this->getActionController();
+
+        if (!isset($controller->dependencies)
+            || !is_array($controller->dependencies)
+        ) {
+            return;
+        }
+
+        foreach ($controller->dependencies as $name) {
+            if ($bootstrap->hasResource($name)) {
+                $controller->$name = $bootstrap->getResource($name);
+            }
+        }    
+    }
+ 
+    public function getBootstrap()
+    {
+        return $this->getFrontController()->getParam('bootstrap');
+    }
+}

+ 7 - 0
demos/Zend/Cloud/cloudexp/public/.htaccess

@@ -0,0 +1,7 @@
+
+RewriteEngine On
+RewriteCond %{REQUEST_FILENAME} -s [OR]
+RewriteCond %{REQUEST_FILENAME} -l [OR]
+RewriteCond %{REQUEST_FILENAME} -d
+RewriteRule ^.*$ - [NC,L]
+RewriteRule ^.*$ index.php [NC,L]

+ 26 - 0
demos/Zend/Cloud/cloudexp/public/index.php

@@ -0,0 +1,26 @@
+<?php
+
+// Define path to application directory
+defined('APPLICATION_PATH')
+    || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
+
+// Define application environment
+defined('APPLICATION_ENV')
+    || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'development'));
+
+// Ensure library/ is on include_path
+set_include_path(implode(PATH_SEPARATOR, array(
+    realpath(APPLICATION_PATH . '/../library'),
+    get_include_path(),
+)));
+
+/** Zend_Application */
+require_once 'Zend/Application.php';
+
+// Create application, bootstrap, and run
+$application = new Zend_Application(
+    APPLICATION_ENV,
+    APPLICATION_PATH . '/configs/application.ini'
+);
+$application->bootstrap()
+            ->run();

+ 19 - 0
documentation/manual/en/manual.xml.in

@@ -476,6 +476,25 @@
             </xi:include>
         </chapter>
 
+        <chapter id="zend.cloud">
+            <title>SimpleCloud API: Zend_Cloud</title>
+            <xi:include href="module_specs/Zend_Cloud_DocumentService.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Cloud_DocumentService.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Cloud_QueueService.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Cloud_QueueService.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Cloud_StorageService.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Cloud_StorageService.xml" />
+                </xi:fallback>
+            </xi:include>
+        </chapter>
+
         <chapter id="zend.codegenerator">
             <title>Zend_CodeGenerator</title>
             <xi:include href="module_specs/Zend_CodeGenerator-Introduction.xml">

+ 984 - 0
documentation/manual/en/module_specs/Zend_Cloud_DocumentService.xml

@@ -0,0 +1,984 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.cloud.documentservice">
+    <title>Document Service Introduction</title>
+
+    <para>
+        <classname>Zend_Cloud_DocumentService</classname> abstracts the interfaces to all major
+        document databases - both in the cloud and locally deployed - so developers can access their
+        common functionality through one API. In other words, an application can make use of these
+        databases and services with no concern over how the application will be deployed. The data
+        source can be chosen through configuration changes alone at the time of deployment.
+    </para>
+
+    <para>
+        Document databases and services are increasingly common in application development. These
+        data sources are somewhat different from traditional relational data sources, as they eschew
+        complex relationships for performance, scalability, and flexibility. Examples of
+        document-oriented services include Amazon SimpleDB and Azure Table Storage.
+    </para>
+
+    <para>
+        The Simple Cloud API offers some flexibility for vendor-specific features with an
+        <varname>$options</varname> array in each method signature.  Some adapters require certain
+        options that also must be added to the <varname>$options</varname> array. It is a good
+        practice to retrieve these options from a configuration file to maintain compatibility with
+        all services and databases; unrecognized options will simply be discarded, making it
+        possible to use different services based on environment.
+    </para>
+
+    <para>
+        If more vendor-specific requirements are required, the developer should extend the specific
+        <classname>Zend_Cloud_DocumentService</classname> adapter to add support for these features.
+        In this manner, vendor-specific features can be called out in the application by referring
+        to the Simple Cloud API extensions in the subclass of the Simple Cloud adapter.
+    </para>
+
+    <sect2 id="zend.cloud.documentservice.adapterinterface">
+        <title>Zend_Cloud_DocumentService_Adapter Interface</title>
+
+        <para>
+            The <classname>Zend_Cloud_DocumentService_Adapter</classname> interface defines methods
+            that each concrete document service adapter implements. The following adapters are
+            shipped with the Simple Cloud API: 
+        </para>
+        
+        <itemizedlist>
+            <listitem>
+                <para>
+                    <ulink url="http://aws.amazon.com/simpledb/"><classname>Zend_Cloud_DocumentService_Adapter_SimpleDb</classname></ulink>
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    <ulink url="http://msdn.microsoft.com/en-us/library/dd179423.aspx"><classname>Zend_Cloud_DocumentService_Adapter_WindowsAzure</classname></ulink>
+                </para>
+            </listitem>
+        </itemizedlist>
+        
+        <para>
+            To instantiate a document service adapter, use the static method
+            <methodname>Zend_Cloud_DocumentService_Factory::getAdapter()</methodname>, which accepts
+            a configuration array or a <classname>Zend_Config</classname> object. The
+            <varname>document_adapter</varname> key should specify the concrete adapter class by
+            classname. Adapter-specific keys may also be passed in this configuration parameter.
+        </para>
+        
+        <example id="zend.cloud.documentservice.factory.example">
+            <title>Example: Using the SimpleDB adapter</title>
+
+            <programlisting language="php"><![CDATA[
+$adapterClass = 'Zend_Cloud_DocumentService_Adapter_SimpleDb';
+$documents = Zend_Cloud_DocumentService_Factory::getAdapter(array(
+    Zend_Cloud_DocumentService_Factory::DOCUMENT_ADAPTER_KEY    => $adapterClass,
+    Zend_Cloud_DocumentService_Adapter_SimpleDb::AWS_ACCESS_KEY => $amazonKey,
+    Zend_Cloud_DocumentService_Adapter_SimpleDb::AWS_SECRET_KEY => $amazonSecret
+));
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.documentservice.adapteroptions">
+        <title>Supported Adapter Options</title>
+
+        <table frame="all" id="zend.cloud.documentservice.options.general">
+            <table>Zend_Cloud_DocumentService_Adapter Common Options</table>
+            <tgroup cols="4">
+                <thead>
+                    <row>
+                        <entry>Option key</entry>
+                        <entry>Description</entry>
+                        <entry>Used in</entry>
+                        <entry>Required</entry>
+                        <entry>Default</entry>
+                    </row>
+                </thead>
+
+                <tbody>
+                    <row>
+                        <entry>document_class</entry>
+                        <entry>
+                            <para>
+                                Class to use to represent returned documents. The class provided must extend
+                                <classname>Zend_Cloud_DocumentService_Document</classname> to ensure
+                                compatibility with all document services. For all methods that
+                                return a document or collection of documents, this class will be
+                                used.
+                            </para>
+                        </entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry><classname>Zend_Cloud_Document_Service_Document</classname></entry>
+                    </row>
+
+                    <row>
+                        <entry>documentset_class</entry>
+                        <entry>
+                            <para>
+                                Class to use to represent collections of documents,
+                                <classname>Zend_Cloud_DocumentService_DocumentSet</classname> by
+                                default. Typically, objects of this class will be returned by
+                                <methodname>listDocuments()</methodname> and
+                                <methodname>query()</methodname>. Any class provided for this
+                                configuration value must extend
+                                <classname>Zend_Cloud_DocumentService_DocumentSet</classname>.
+                            </para>
+                        </entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry><classname>Zend_Cloud_DocumentService_DocumentSet</classname></entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+
+        <table frame="all" id="zend.cloud.documentservice.options.sdb">
+            <title>Zend_Cloud_DocumentService_Adapter_SimpleDb Options</title>
+
+            <tgroup cols="4">
+                <thead>
+                    <row>
+                        <entry>Option key</entry>
+                        <entry>Description</entry>
+                        <entry>Used in</entry>
+                        <entry>Required</entry>
+                        <entry>Default</entry>
+                    </row>
+                </thead>
+
+                <tbody>
+                    <row>
+                        <entry>query_class</entry>
+                        <entry>
+                            <para>
+                                Class to use for creating and assembling queries for this document
+                                service; <methodname>select()</methodname> will create objects of
+                                this class name, as will <methodname>listDocuments()</methodname>.
+                            </para>
+                        </entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry><classname>Zend_Cloud_DocumentService_Adapter_SimpleDb_Query</classname></entry>
+                    </row>
+
+                    <row>
+                        <entry>aws_accesskey</entry>
+                        <entry>Your Amazon AWS access key</entry>
+                        <entry>Constructor</entry>
+                        <entry>Yes</entry>
+                        <entry>None</entry>
+                    </row>
+
+                    <row>
+                        <entry>aws_secretkey</entry>
+                        <entry>Your Amazon AWS secret key</entry>
+                        <entry>Constructor</entry>
+                        <entry>Yes</entry>
+                        <entry>None</entry>
+                    </row>
+
+                    <row>
+                        <entry>http_adapter</entry>
+                        <entry>HTTP adapter to use in all access operations</entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry><classname>Zend_Http_Client_Adapter_Socket</classname></entry>
+                    </row>
+
+                    <row>
+                        <entry>merge</entry>
+                        <entry>
+                            <para>
+                                If a boolean true, all attribute values are merged.  You may also
+                                specify an array of key pairs, where the key is the attribute key to
+                                merge, and the value indicates whether or not to merge; a boolean
+                                true value will merge the given key. Any attributes not specified in
+                                this array will be replaced.
+                            </para>
+                        </entry>
+                        <entry><methodname>updateDocument()</methodname></entry>
+                        <entry>No</entry>
+                        <entry>True</entry>
+                    </row>
+
+                    <row>
+                        <entry>return_documents</entry>
+                        <entry>
+                            <para>
+                                If a boolean true, <methodname>query()</methodname> returns a
+                                <classname>Zend_Cloud_DocumentService_DocumentSet</classname> object
+                                containing
+                                <classname>Zend_Cloud_DocumentService_Document</classname> objects
+                                (default case); otherwise, it returns an array of arrays.
+                            </para>
+                        </entry>
+                        <entry><methodname>query()</methodname></entry>
+                        <entry>No</entry>
+                        <entry>True</entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>    
+
+        <table frame='all' id="zend.cloud.documentservice.options.azure">
+            <title>Zend_Cloud_DocumentService_Adapter_WindowsAzure Options</title>
+
+            <tgroup cols="4">
+                <thead>
+                    <row>
+                        <entry>Option key</entry>
+                        <entry>Description</entry>
+                        <entry>Used in</entry>
+                        <entry>Required</entry>
+                        <entry>Default</entry>
+                    </row>
+                </thead>
+
+                <tbody>
+                    <row>
+                        <entry>query_class</entry>
+                        <entry>
+                            <para>
+                                Class to use for creating and assembling queries for this document
+                                service; <methodname>select()</methodname> will create objects of
+                                this class name, as will <methodname>listDocuments()</methodname>.
+                            </para>
+                        </entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry><classname>Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query</classname></entry>
+                    </row>
+
+                    <row>
+                        <entry>default_partition_key</entry>
+                        <entry>
+                            <para>
+                                The default partition key to use if none is specified in the
+                                document identifier. Windows Azure requires a two-fold document ID,
+                                consisting of a PartitionKey and a RowKey. The PartitionKey will
+                                typically be common across your database or a collection, while the
+                                RowKey will vary. As such, this setting allows you to specify the
+                                default PartitionKey to utilize for all documents.
+                            </para>
+
+                            <para>
+                                If not specified, the adapter will default to using the collection
+                                name as the PartitionKey.
+                            </para>
+                        </entry>
+                        <entry>Constructor, <methodname>setDefaultPartitionKey()</methodname></entry>
+                        <entry>Name of whatever collection the document belongs to</entry>
+                    </row>
+
+                    <row>
+                        <entry>storage_accountname</entry>
+                        <entry>Windows Azure account name</entry>
+                        <entry>Constructor</entry>
+                        <entry>Yes</entry>
+                        <entry>None</entry>
+                    </row>
+
+                    <row>
+                        <entry>storage_accountkey</entry>
+                        <entry>Windows Azure account key</entry>
+                        <entry>Constructor</entry>
+                        <entry>Yes</entry>
+                        <entry>None</entry>
+                    </row>
+
+                    <row>
+                        <entry>storage_host</entry>
+                        <entry>
+                            <para>
+                                Windows Azure access host, default is
+                                <varname>table.core.windows.net</varname>
+                            </para>
+                        </entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry><varname>table.core.windows.net</varname></entry>
+                    </row>
+
+                    <row>
+                        <entry>storage_proxy_host</entry>
+                        <entry>Proxy hostname</entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry>None</entry>
+                    </row>
+
+                    <row>
+                        <entry>storage_proxy_port</entry>
+                        <entry>Proxy port</entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry>8080</entry>
+                    </row>
+
+                    <row>
+                        <entry>storage_proxy_credentials</entry>
+                        <entry>Proxy credentials</entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry>None</entry>
+                    </row>
+
+                    <row>
+                        <entry>HTTP Adapter</entry>
+                        <entry>HTTP adapter to use in all access operations</entry>
+                        <entry>Constructor</entry>
+                        <entry>No</entry>
+                        <entry>None</entry>
+                    </row>
+
+                    <row>
+                        <entry>verify_etag</entry>
+                        <entry>
+                            <para>
+                                Verify ETag on the target document and perform the operation only if the
+                                ETag matches the expected value
+                            </para>
+                        </entry>
+                        <entry>
+                            <methodname>updateDocument()</methodname>,
+                            <methodname>replaceDocument()</methodname>,
+                            <methodname>deleteDocument()</methodname>
+                        </entry>
+                        <entry>No</entry>
+                        <entry>False</entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+    </sect2>
+
+    <sect2 id="zend.cloud.documentservice.concepts">
+        <title>Basic concepts</title>
+
+        <para>
+            Each document-oriented service and database uses its own terminology and constructs in
+            its API. The SimpleCloud API identifies and abstracts a number of common concepts and
+            operations that are shared among providers.
+        </para>
+
+        <para>
+            Document storage consists of a number of <emphasis>collections</emphasis>, which are
+            logical storage units analogous to database tables in the SQL world. Collections contain
+            <emphasis>documents</emphasis>, which are essentially a set of key-value pairs, along
+            with some metadata specific to the storage engine, and are identified by a unique
+            <emphasis>document ID</emphasis>.
+        </para>
+
+        <para>
+            Each document has its own structure (set of fields) that does not necessarily have to
+            match the structure of any other document, even in the same collection. In fact, you can
+            change this structure after the document is created.
+        </para>
+
+        <para>
+            Documents can be retrieved by ID or by querying a collection.
+        </para>
+
+        <para>
+            Documents are represented by the class
+            <classname>Zend_Cloud_DocumentService_Document</classname>.  Note that the document
+            class does not validate the supplied IDs and data, and does not enforce compatibility
+            with each adapter's requirements.
+        </para> 
+
+        <para>
+            The document fields can be accessed using keys as object properties and as array
+            elements.
+        </para>
+
+        <para>
+            The basic interface of <classname>Zend_Cloud_DocumentService_Document</classname> is as
+            follows:
+        </para>
+
+        <programlisting lang="php"><![CDATA[
+/**
+ * ArrayAccess allows accessing fields by array key:
+ *    $doc['fieldname']
+ *
+ * IteratorAggregate allows iterating over all fields:
+ *    foreach ($document as $field => $value) {
+ *        echo "$field: $value\n";
+ *    }
+ *
+ * Countable provides a count of all fields:
+ *    count($document)
+ */
+class Zend_Cloud_DocumentService_Document
+    implements ArrayAccess, IteratorAggregate, Countable
+{
+    const KEY_FIELD = '_id';
+
+    /** 
+     * $fields may be an array or an object implementing ArrayAccess. 
+     * If no $id is provided, it will look for a field matching KEY_FIELD to 
+     * use as the identifier.
+     */
+    public function __construct($fields, $id = null);
+
+    public function setId($id);
+    public function getId();
+    public function getFields();
+    public function getField($name);
+    public function setField($name, $value);
+
+    /**
+     * These allow overloading, so you may access fields as if they were 
+     * native properties of the document
+     */
+    public function __get($name);
+    public function __set($name, $value);
+
+    /**
+     * Alternately, you can acces fields as if via native getters and
+     * setters:
+     *     $document->setFoo($value);    // set "Foo" field to value
+     *     $value = $document->getFoo(); // get "Foo" field value
+    public function __call($name, $args);
+}
+]]></programlisting>
+
+        <note>
+            <title>Windows Azure Document Identifiers</title>
+
+            <para> 
+                Windows Azure technically requires a combination of two fields to uniquely
+                identify documents: the <varname>PartitionKey</varname> and
+                <varname>RowKey</varname>, and as such, keys are fully qualified by the structure
+                <code>array(PartitionKey, RowKey)</code> -- which makes them non-portable.  In most
+                situations, the <varname>PartitionKey</varname> will not differ for documents in a
+                single collection -- and potentially not even across your entire table instance. As
+                such, the DocumentService provides several options for specifying keys:
+            </para>
+
+            <itemizedlist>
+                <listitem>
+                    <para>
+                        Array keys will always work as expected.
+                    </para>
+                </listitem>
+
+                <listitem>
+                    <para>
+                        If a string key is provided:
+                    </para>
+
+                    <itemizedlist>
+                        <listitem>
+                            If the <varname>default_partition_key</varname> setting was provided to
+                            the constructor, or passed to the
+                            <methodname>setDefaultPartitionKey()</methodname> method, that value
+                            will be used for the <varname>PartitionKey</varname>.
+                        </listitem>
+
+                        <listitem>
+                            Otherwise, the name of the collection on which you are operating will be
+                            used.
+                        </listitem>
+                    </itemizedlist>
+                </listitem>
+            </itemizedlist>
+
+            <para>
+                The takeaway is that you can utilize string keys if you wish to maximize portability
+                of your application. Just be aware that your record will contain a few extra fields
+                to denote the key (<varname>PartitionKey</varname>, <varname>RowKey</varname>, and
+                the previously undiscussed <varname>Timestamp</varname>) should you ever migrate
+                your data to another service.
+            </para>
+        </note>
+
+        <example id="zend.cloud.documentservice.document.create.example">
+            <title>Creating a document</title>
+
+            <programlisting language="php"><![CDATA[
+$document = new Zend_Cloud_DocumentService_Document(array(
+    'key1' => 'value1',
+    'key2' => 123,
+    'key3' => 'thirdvalue',
+), "DocumentId");
+$document->otherkey = 'some more data';
+echo "key 1: " . $document->key1   . "\n"; // object notation
+echo "key 2: " . $document['key2'] . "\n"; // array notation
+]]></programlisting>
+        </example>
+
+        <example id="zend.cloud.documentservice.document.explore.example">
+            <title>Exploring the document data</title>
+
+            <programlisting language="php"><![CDATA[
+$document = $documents->fetchDocument("mydata", $id);
+echo "Document ID: " . $document->getID() . "\n";
+foreach ($document->getFields() as $key => $value) {
+    echo "Field $key is $value\n";
+}              
+]]></programlisting>
+        </example>
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.exceptions">
+        <title>Exceptions</title>
+
+        <para>
+            If some error occurs in the document service,
+            <classname>Zend_Cloud_DocumentService_Exception</classname> is thrown.  If the exception
+            was caused by the underlying service driver, you can use the 
+            <methodname>getClientException()</methodname> method to retrieve the original exception. 
+        </para>
+
+        <para>
+            Since different cloud providers implement different sets of services, some drivers do
+            not implement certain features. In this case, the
+            <classname>Zend_Cloud_OperationNotAvailableException</classname> exception is thrown.
+        </para>
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.create-collection">
+        <title>Creating a collection</title>
+
+        <para>
+            A new collection is created using <methodname>createCollection()</methodname>.
+        </para>
+
+        <example id="zend.cloud.documentservice.create-collection.example">
+            <title>Creating collection</title>
+
+            <programlisting language="php"><![CDATA[
+$documents->createCollection("mydata");
+]]></programlisting>
+        </example>
+
+        <para>
+            If you call <methodname>createCollection()</methodname> with a collection name that
+            already exists, the service will do nothing and leave the existing collection untouched.
+        </para>
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.delete-collection">
+        <title>Deleting a collection</title>
+
+        <para>
+            A collection is deleted by calling <methodname>deleteCollection()</methodname>.
+        </para>
+
+        <example id="zend.cloud.documentservice.delete-collection.example">
+            <title>Deleting a collection</title>
+
+            <programlisting language="php"><![CDATA[
+$documents->deleteCollection("mydata");
+]]></programlisting>
+        </example>
+
+        <para>
+            Deleting a collection automatically deletes all documents contained in that collection.
+        </para>
+
+        <note>
+            <para>
+                Deleting a collection can take significant time for some services. You cannot
+                re-create a collection with the same name until the collection and all its documents
+                have been completely removed.
+            </para>
+        </note>
+
+        <para>
+            Deleting a non-existent collection will have no effect.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.cloud.documentservice.list-collections">
+        <title>Listing available collections</title>
+
+        <para>
+            A list of existing collections is returned by
+            <methodname>listCollections()</methodname>.  This method returns an array of all the
+            names of collections belonging to the account you specified when you created the
+            adapter.
+        </para>
+        
+        <example id="zend.cloud.documentservice.list-collections.example">
+            <title>List collections</title>
+
+            <programlisting language="php"><![CDATA[
+$list = $documents->listCollections();
+foreach ($list as $collection) {
+    echo "My collection: $collection\n";
+}
+]]></programlisting>
+        </example>
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.insert">
+        <title>Inserting a document</title>
+
+        <para>
+            To insert a document, you need to provide a
+            <classname>Zend_Cloud_DocumentService_Document</classname> object or associative array
+            of data, as well as the collection in which you are inserting it.
+        </para>
+            
+        <para>
+            Many providers require that you provide a document ID with your document. If using a
+            <classname>Zend_Cloud_DocumentService_Document</classname>, you can specify this by
+            passing the identifier to the constructor when you instantiate the object. If using an
+            associative array, the key name will be adapter-specific locations; for example, on
+            Azure, the ID is made up of the PartitionKey and RowKey; on Amazon SimpleDB, the ID is
+            the ItemName; you may also specify the key in the <varname>_id</varname> field to be
+            more portable.
+        </para>
+
+        <para>
+            As such, the easiest and most compatible way to specify the key is to use
+            a Document object.
+        </para>
+
+        <example id="zend.cloud.documentservice.insert.example">
+            <title>Inserting a document</title>
+
+            <programlisting language="php"><![CDATA[
+// Instantiating and creating the document
+$document = new Zend_Cloud_DocumentService_Document(array(
+    'key1' => 'value1',
+    'key2' => 123,
+    'key3' => 'thirdvalue',
+), "DocumentID");
+
+// inserting into the "mydata" collection
+$documents->insertDocument("mydata", $document);
+]]></programlisting>
+        </example>            
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.replace">
+        <title>Replacing a document</title>
+
+        <para>
+            <emphasis>Replacing</emphasis> a document means removing all document data associated with a particular
+            document key and substituting it with a new set of data. Unlike
+            <emphasis>updating</emphasis>, this operation does not merge old and new data but
+            replaces the document as a whole. The replace operation, like
+            <methodname>insertDocument()</methodname>, accepts a
+            <classname>Zend_Cloud_DocumentService_Document</classname> document or an array of
+            key-value pairs that specify names and values of the new fields, and the collection in
+            which the document exists.
+        </para>
+
+        <note>
+            <title>Document ID is required</title>
+
+            <para>
+                To replace the document, the document ID is required. Just like inserting a document,
+                if you use an associative array to describe the document, you will need to provide a
+                provider-specific key indicating the document ID. As such, the most compatible way
+                to replace a document across providers is to utilize a Document object, as shown in
+                the examples.
+            </para>
+        </note>
+
+        <example id="zend.cloud.documentservice.replace.example">
+            <title>Replacing a document</title>
+
+            <programlisting language="php"><![CDATA[
+$document = new Zend_Cloud_DocumentService_Document(array(
+    'key1' => 'value1',
+    'key2' => 123,
+    'key3' => 'thirdvalue',
+), "DocumentID");
+
+// Update the document as found in the "mydata" collection
+$documents->replaceDocument("mydata", $document);            
+]]></programlisting>
+
+            <para>
+                You may also use an existing Document object, re-assign the fields and/or assign new
+                fields, and pass it to the <methodname>replaceDocument()</methodname> method:
+            </para>
+
+            <programlisting language="php"><![CDATA[
+$docment->key4 = '4th value';
+
+// Update the document as found in the "mydata" collection
+$documents->replaceDocument("mydata", $document);            
+]]></programlisting>
+        </example>            
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.update">
+        <title>Updating a document</title>
+
+        <para>
+            <emphasis>Updating</emphasis> a document changes the key/value pairs in an existing
+            document. This operation does not share the <emphasis>replace</emphasis> semantics; the
+            values of the keys that are not specified in the data set will not be changed. You must
+            provide both a document key and data, either via a 
+            <classname>Zend_Cloud_DocumentService_Document</classname> document or an array, to this
+            method. If the key is null and a document object is provided, the document key is used.
+        </para>
+
+        <example id="zend.cloud.documentservice.update.example">
+            <title>Updating a document</title>
+
+            <programlisting language="php"><![CDATA[
+// update one field
+$documents->updateDocument("mydata", "DocumentID", array("key2" => "new value"));
+
+// or with document; this could be a document already retrieved from the service
+$document = new Zend_Cloud_DocumentService_Document(array(
+    'key1' => 'value1',
+    'key2' => 123,
+    'key3' => 'thirdvalue',
+), "DocumentID");
+$documents->updateDocument("mydata", null, $document);
+
+// copy document to another ID
+$documents->updateDocument("mydata", "AnotherDocumentID", $document);
+]]></programlisting>
+        </example>            
+
+        <para>
+            Amazon SimpleDB supports multi-value fields, so data updates will be merged with the old key
+            value instead of replacing them. Option <property>merge</property> should contain an array
+            of field names to be merged. The array should be key/value pairs, with the key
+            corresponding to the field key, and the value a boolean value indicating merge status
+            (boolean true would merge; false would not). Any keys not specified in the
+            <property>merge</property> option will be replaced instead of merged.
+        </para>
+
+        <example id="zend.cloud.documentservice.update.merge.example">
+            <title>Merging document fields</title>
+
+            <programlisting language="php"><![CDATA[
+// key2 is overwritten, key3 is merged
+$documents->updateDocument('mydata', 'DocumentID', 
+    array('key2' => 'new value', 'key3' => 'additional value'), 
+    array('merge' => array('key3' => true))
+);
+]]></programlisting>
+        </example>
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.delete">
+        <title>Deleting a document</title>
+
+        <para>
+            A document can be deleted by passing its key to
+            <methodname>deleteDocument()</methodname>.  Deleting a non-existant document has no
+            effect.
+        </para>
+
+        <example id="zend.cloud.documentservice.delete.example">
+            <title>Deleting a document</title>
+
+            <programlisting language="php"><![CDATA[
+$documents->deleteDocument("collectionName", "DocumentID");            
+]]></programlisting>
+        </example>            
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.fetch">
+        <title>Fetching a document</title>
+
+        <para>
+            You can fetch a specific document by specifying its key.
+            <methodname>fetchDocument()</methodname> returns one instance of
+            <classname>Zend_Cloud_DocumentService_Document</classname>.
+        </para>
+
+        <example id="zend.cloud.documentservice.fetch.example">
+            <title>Fetching a document</title>
+
+            <programlisting language="php"><![CDATA[
+$document = $service->fetchDocument('collectionName', 'DocumentID');
+echo "Document ID: " . var_export($document->getID(), 1) . "\n";
+foreach ($document->getFields() as $key => $value) {
+    echo "Field $key is $value\n";
+}
+]]></programlisting>
+        </example>            
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.query">
+        <title>Querying a collection</title>
+
+        <para>
+            To find documents in the collection that meet some criteria, use the
+            <methodname>query()</methodname>method. This method accepts either a string which is an
+            adapter-dependent query and is passed as-is to the concrete adapter, or a structured query
+            object instance of <classname>Zend_Cloud_DocumentService_Query</classname>. The return
+            is a <classname>Zend_Cloud_DocumentService_DocumentSet</classname>, containing instances
+            of <classname>Zend_Cloud_DocumentService_Document</classname> that satisfy the query.
+            The DocumentSet object is iterable and countable.
+        </para>
+
+        <example id="zend.cloud.documentservice.query.example">
+            <title>Querying a collection using a string query</title>
+
+            <programlisting language="php"><![CDATA[
+$docs = $documents->query(
+    "collectionName", 
+    "RowKey eq 'rowkey2' or RowKey eq 'rowkey2'"
+);
+
+foreach ($docs as $doc) {
+    $id = $doc->getId();
+    echo "Found document with ID: " 
+        . var_export($id, 1)
+        . "\n";
+}            
+]]></programlisting>
+        </example>            
+
+        <para>
+            If using a structured query object, typically, you will retrieve it using the
+            <methodname>select()</methodname> method. This ensures that the query object is specific
+            to your adapter, which will ensure that it is assembled into a syntax your adapter
+            understands.
+        </para>
+
+        <example id="zend.cloud.documentservice.query.struct-example">
+            <title>Querying a collection with structured query</title>
+
+            <programlisting language="php"><![CDATA[
+$query = $service->select();
+$query->from('collectionName')
+      ->where('year > ?', array(1945))
+      ->limit(3);
+$docs = $documents->query('collectionName', $query);
+
+foreach ($docs as $doc) {
+    $id = $doc->getId();
+    echo "Found document with ID: " 
+        . var_export($id, 1)
+        . "\n";
+}            
+]]></programlisting>
+        </example>            
+
+        <para>
+            <classname>Zend_Cloud_DocumentService_Query</classname> classes do not limit which query
+            clauses can be used, but the clause must be supported by the underlying concrete
+            adapter. Currently supported clauses include:
+        </para>
+
+        <itemizedlist>
+            <listitem>
+                <para>
+                    <methodname>select()</methodname> - defines which fields are returned in the
+                    result. 
+                </para>
+
+                <note>
+                    <para>
+                        Windows Azure ignores this clause's argument and always returns the whole
+                        document.
+                    </para>
+                </note>
+            </listitem> 
+
+            <listitem>
+                <para>
+                    <methodname>from()</methodname> - defines the collection name used in the query.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    <methodname>where()</methodname> - defines the conditions of the query. It
+                    accepts three parameters: condition, array of arguments to replace "?" fields in
+                    the condition, and a conjunction argument which should be "and" or "or", and
+                    which will be used to join this condition with previous conditions.  Multiple
+                    <methodname>where()</methodname> clasues may be specified.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    <methodname>whereId()</methodname> - defines the condition by document ID (key).
+                    The document matching must have the same key. The method accepts one argument -
+                    the required ID (key).
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    <methodname>limit()</methodname> - limits the returned data to specified number
+                    of documents.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    <methodname>order()</methodname> - sorts the returned data by specified field.
+                    Accepts two arguments - first is the field name and second is 'asc' or 'desc'
+                    specifying the sort direction. 
+                </para>
+
+                <note>
+                    <para>
+                        This clause is not currently supported by Windows Azure.
+                    </para>
+                </note>
+            </listitem>
+        </itemizedlist>
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.select">
+        <title>Creating a query</title>
+
+        <para>
+            For the user's convenience, the <methodname>select()</methodname> method instantiates a
+            query object specific to the adapter, and sets the SELECT clause for it.
+        </para>
+
+        <example id="zend.cloud.documentservice.select.example">
+            <title>Creating a structured query</title>
+
+            <programlisting language="php"><![CDATA[
+$query = $documents->select()
+                   ->from('collectionName')
+                   ->where('year > ?', array(1945))
+                   ->limit(3);
+$docs = $documents->query('collectionName', $query);
+foreach ($docs as $doc) {
+    $id = $doc->getId();
+    echo "Found document with ID: " 
+        . var_export($id, 1)
+        . "\n";
+}            
+]]></programlisting>
+        </example>            
+    </sect2>
+    
+    <sect2 id="zend.cloud.documentservice.adapter">
+        <title>Accessing concrete adapters</title>
+
+        <para>
+            Sometimes it is necessary to retrieve the concrete adapter for the service that the
+            Document API is working with. This can be achieved by using the
+            <methodname>getAdapter()</methodname> method.
+        </para>
+
+        <note>
+            <para>
+                Accessing the underlying adapter breaks portability among services, so it should be
+                reserved for exceptional circumstances only.
+            </para>
+        </note>
+
+        <example id="zend.cloud.documentservice.adapter.example">
+            <title>Using concrete adapters</title>
+
+            <programlisting language="php"><![CDATA[
+// Since SimpleCloud Document API doesn't support batch upload, use concrete adapter 
+$amazonSdb = $documents->getAdapter();
+$amazonSdb->batchPutAttributes($items, 'collectionName');
+]]></programlisting>
+        </example>
+    </sect2>
+</sect1>

+ 536 - 0
documentation/manual/en/module_specs/Zend_Cloud_QueueService.xml

@@ -0,0 +1,536 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.cloud.queueservice">
+    <title>Queue Service Introduction</title>
+
+    <para>
+        The QueueService implements access to message queues available as local or remote services.
+        The simple queues that QueueService supports implement a messaging pattern that enables
+        different processes to exchange messages in a reliable and scalable way. One common use case
+        for such message queues is job dispatching, in which a frontend web server adds a complex
+        job to a queue for a backend worker to do the expensive processing. The frontend web server
+        can then return the page without waiting for the work to be completed.
+    </para>
+
+    <para>
+        The interface <classname>Zend_Cloud_QueueService_Adapter</classname> defines the methods
+        which concrete queue service adapters must implement. The following adapters are shipped
+        with the Simple Cloud API: 
+    </para>
+    
+    <itemizedlist>
+        <listitem>
+            <ulink url="http://aws.amazon.com/sqs/"><classname>Zend_Cloud_QueueService_Adapter_Sqs</classname></ulink>
+        </listitem>
+
+        <listitem>
+            <ulink url="http://msdn.microsoft.com/en-us/library/dd179363.aspx"><classname>Zend_Cloud_QueueService_Adapter_WindowsAzure</classname></ulink>
+        </listitem>
+
+        <listitem>
+            <ulink url="http://aws.amazon.com/sqs/"><classname>Zend_Cloud_QueueService_Adapter_ZendQueue</classname></ulink>
+        </listitem>
+    </itemizedlist>
+    
+    <sect2 id="zend.cloud.queueservice.adapters">
+        <title>Instantiating and Configuring QueueService Adapters</title>
+
+        <para>
+            To instantiate a QueueService adapter, use the static method 
+            <methodname>Zend_Cloud_QueueService_Factory::getAdapter()</methodname>, which accepts
+            either an array or a <classname>Zend_Config</classname> object. Three parameters apply to all adapters, while 
+            the remaining
+            parameters are adapter-specific properties; these adapter-specific properties often
+            contain access details.
+        </para>
+
+        <para>
+            The general parameters are as follows:
+        </para>
+
+        <itemizedlist>
+            <listitem>
+                <varname>queue_adapter</varname> specifies the concrete adapter class;
+            </listitem>
+
+            <listitem>
+                <varname>message_class</varname> specifies the class to use to
+                represent queue messages; defaults to
+                <classname>Zend_Cloud_QueueService_Message</classname>; and
+            </listitem>
+
+            <listitem>
+                <varname>messageset_class</varname> specifies the class to use
+                to represent collections of queue messages; defaults to
+                <classname>Zend_Cloud_QueueService_MesageSet</classname>.
+            </listitem>
+        </itemizedlist>
+        
+        <example id="zend.cloud.queueservice.factory.example">
+            <title>Instantiating an Amazon SQS adapter via the factory</title>
+
+            <programlisting language="php"><![CDATA[
+$queues = Zend_Cloud_QueueService_Factory::getAdapter(array(
+    Zend_Cloud_QueueService_Factory::QUEUE_ADAPTER_KEY  => 'Zend_Cloud_QueueService_Adapter_Sqs',
+    Zend_Cloud_QueueService_Adapter_Sqs::AWS_ACCESS_KEY => $amazonKey,
+    Zend_Cloud_QueueService_Adapter_Sqs::AWS_SECRET_KEY => $amazonSecret,
+));
+]]></programlisting>
+        </example>
+        
+        <sect3 id="zend.cloud.queueservice.adapters.options">
+            <title>Service-Specific Options</title>
+           
+            <table frame='all' id="zend.cloud.queueservice.adapters.options.sqs">
+                <title>Zend_Cloud_QueueService_Adapter_Sqs Options</title>
+
+                <tgroup cols="4">
+                    <thead>
+                        <row>
+                            <entry>Option key</entry>
+                            <entry>Description</entry>
+                            <entry>Used in</entry>
+                            <entry>Required</entry>
+                            <entry>Default</entry>
+                        </row>
+                    </thead>
+
+                    <tbody>
+                        <row>
+                            <entry>aws_accesskey</entry>
+                            <entry>Your Amazon AWS access key</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>aws_secretkey</entry>
+                            <entry>Your Amazon AWS secret key</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>http_adapter</entry>
+                            <entry>HTTP adapter to use in all access operations</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry><classname>Zend_Http_Client_Adapter_Socket</classname></entry>
+                        </row>
+                        
+                        <row>
+                            <entry>http_adapter</entry>
+                            <entry>HTTP adapter to use in all access operations</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry><classname>Zend_Http_Client_Adapter_Socket</classname></entry>
+                        </row>
+
+                        <row>
+                            <entry>visibility_timeout</entry>
+                            <entry>Message visibility timeout</entry>
+                            <entry><methodname>receiveMessages()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>60</entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </table>    
+
+            <table frame='all' id="zend.cloud.queueservice.adapters.options.azure">
+                <title>Zend_Cloud_QueueService_Adapter_WindowsAzure Options</title>
+
+                <tgroup cols="4">
+                    <thead>
+                        <row>
+                            <entry>Option key</entry>
+                            <entry>Description</entry>
+                            <entry>Used in</entry>
+                            <entry>Required</entry>
+                            <entry>Default</entry>
+                        </row>
+                    </thead>
+
+                    <tbody>
+                        <row>
+                            <entry>storage_accountname</entry>
+                            <entry>Windows Azure account name</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_accountkey</entry>
+                            <entry>Windows Azure account key</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_host</entry>
+                            <entry>Windows Azure access host</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry><varname>queue.core.windows.net</varname></entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_proxy_host</entry>
+                            <entry>Proxy hostname</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_proxy_port</entry>
+                            <entry>Proxy port</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry>8080</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_proxy_credentials</entry>
+                            <entry>Proxy credentials</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>http_adapter</entry>
+                            <entry>HTTP adapter to use in all access operations</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry><classname>Zend_Http_Client_Adapter_Socket</classname></entry>
+                        </row>
+
+                        <row>
+                            <entry>visibility_timeout</entry>
+                            <entry>Message visibility timeout</entry>
+                            <entry><methodname>receiveMessages()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>60</entry>
+                        </row>
+
+                        <row>
+                            <entry>prefix</entry>
+                            <entry>
+                                Filter the results to only queue names beginning with given prefix
+                            </entry>
+                            <entry><methodname>listQueues()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>max_results</entry>
+                            <entry>Limit queue list to certain number of results</entry>
+                            <entry><methodname>listQueues()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>5,000</entry>
+                        </row>
+
+                        <row>
+                            <entry>ttl</entry>
+                            <entry>Set time-to-live for message</entry>
+                            <entry><methodname>sendMessage()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>7 days</entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </table>
+
+            <table frame='all' id="zend.cloud.queueservice.adapters.options.zend-queue">
+                <title>Zend_Cloud_QueueService_Adapter_ZendQueue Options</title>
+
+                <tgroup cols="4">
+                    <thead>
+                        <row>
+                            <entry>Option key</entry>
+                            <entry>Description</entry>
+                            <entry>Used in</entry>
+                            <entry>Required</entry>
+                            <entry>Default</entry>
+                        </row>
+                    </thead>
+
+                    <tbody>
+                        <row>
+                            <entry>adapter</entry>
+                            <entry>
+                                Concrete <classname>Zend_Queue</classname> adapter to use. See the
+                                <link linkend="zend.queue">Zend_Queue</link> documentation for supported
+                                adapters and their options.</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry>Filesystem</entry>
+                        </row>
+
+                        <row>
+                            <entry>timeout</entry>
+                            <entry>Visibility timeout for messages</entry>
+                            <entry>
+                                <methodname>createQueue()</methodname>,
+                                <methodname>receiveMessages()</methodname>
+                            </entry>
+                            <entry>No</entry>
+                            <entry>30</entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </table>
+        </sect3>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.concepts">
+        <title>Basic concepts</title>
+
+        <para>
+            Every queue service typically offers one or more <emphasis>queues</emphasis>. Each queue
+            can store zero or more <emphasis>messages</emphasis>. A process can send a message to a
+            queue, and another process can remove it. Usually processes remove the oldest message in
+            the queue, observing a first in, first out (FIFO) queue-style interface.
+        </para>
+    </sect2>
+    
+    <sect2 id="zend.cloud.queueservice.exceptions">
+        <title>Exceptions</title>
+
+        <para>
+            If some error occurs inside the storage service, a
+            <classname>Zend_Cloud_QueueService_Exception</classname> is thrown. If the exception was
+            caused by underlying service driver, you can use the
+            <methodname>getClientException()</methodname> method to retrieve the original exception. 
+        </para>
+
+        <para>
+            Since different cloud providers implement different sets of services, some adapters do
+            not implement certain features. In this case, the
+            <classname>Zend_Cloud_OperationNotAvailableException</classname> exception is thrown.
+        </para>
+    </sect2>
+    
+    <sect2 id="zend.cloud.queueservice.create-queue">
+        <title>Create a queue</title>
+
+        <para>
+            The <methodname>createQueue()</methodname> method creates a message queue with the given
+            name.  It returns a queue identifier, the format of which is service-dependent.  Some
+            services return a URL for the queue identifier, while others return a GUID to use in
+            future operations.
+        </para>
+
+        <example id="zend.cloud.queueservice.create-queue.example">
+            <title>Creating a queue</title>
+
+            <programlisting language="php"><![CDATA[
+$queueId = $queues->createQueue('my-queue');            
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.delete-queue">
+        <title>Delete a queue</title>
+
+        <para>
+            The <methodname>deleteQueue()</methodname> method removes the queue from the service.
+            You must use the identifier received from <methodname>createQueue()</methodname> when
+            calling <methodname>deleteQueue()</methodname>.
+        </para>
+
+        <example id="zend.cloud.queueservice.delete-queue.example">
+              <title>Deleting a queue</title>
+
+            <programlisting language="php"><![CDATA[
+$queueId = $queues->createQueue('my-queue');
+
+// ... do stuff ...
+
+$queues->deleteQueue($queueId);        
+]]></programlisting>
+        </example>
+
+        <note>
+            <para>
+                Deleting a queue can take significant time for some services. Typically, you cannot
+                re-create a queue with the same name until the original queue is fully removed.
+            </para>
+        </note>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.list">
+        <title>List queues</title>
+
+        <para>
+            To retrieve the list of all queues in the system, use the
+            <methodname>listQueues()</methodname> method.
+        </para>
+
+        <example id="zend.cloud.queueservice.list.example">
+              <title>Listing queues</title>
+
+            <programlisting language="php"><![CDATA[
+$names = $queues->listQueues();
+foreach ($names as $name) {
+    echo "Found queue $name\n";
+}        
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.store-metadata">
+        <title>Set queue metadata</title>
+
+        <para>
+            In some services, you can associate a set of key-value pairs with the queue as queue
+            metadata. To set queue metadata, use the <methodname>storeQueueMetadata()</methodname>
+            method:
+        </para>
+
+        <example id="zend.cloud.queueservice.store-metadata.example">
+              <title>Setting queue metadata</title>
+
+            <programlisting language="php"><![CDATA[
+$queues->storeQueueMetadata($queueId, array(
+    'purpose'       => 'Operations', 
+    'administrator' => 'joe@example.com',
+));
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.fetch-metadata">
+        <title>Fetch queue metadata</title>
+
+        <para>
+            To retrieve queue metadata, use the <methodname>fetchQueueMetadata()</methodname>
+            method.
+        </para>
+
+        <example id="zend.cloud.queueservice.fetch-metadata.example">
+            <title>Fetching queue metadata</title>
+
+            <programlisting language="php"><![CDATA[
+$metadata = $queues->fetchQueueMetadata($queueId);
+foreac h($metadata as $key => $value) {
+    echo "Metadata $key: $value\n";
+}        
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.send">
+        <title>Send a message</title>
+
+        <para>
+            To add a message to a queue, use the <methodname>sendMessage()</methodname> method.  The
+            message is passed as an unstructured string.
+        </para>
+
+        <example id="zend.cloud.queueservice.send.example">
+              <title>Sending a message</title>
+
+            <programlisting language="php"><![CDATA[
+$queues->sendMessage($queueId, "Hello world!");            
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.receive">
+        <title>Receive a message</title>
+
+        <para>
+            To receive one or more messages from the queue, use the
+            <methodname>receiveMessages()</methodname> method. This method returns a
+            <classname>Zend_Cloud_QueueService_MessageSet</classname> instance by default, unless
+            configured otherwise. Each element of the MessageSet is an instance of
+            <classname>Zend_Cloud_QueueService_Message</classname> by default, unless configuired
+            otherwise.
+        </para>
+
+        <example id="zend.cloud.queueservice.receive.example">
+              <title>Receiving a message</title>
+            <programlisting language="php"><![CDATA[
+// Get the first message
+$messages = $queues->receiveMessages($queueId);
+if (count($messages)) {
+    foreach ($messages as $message) {
+        echo "Message: " . $message->getBody();
+        break;
+    }
+}
+
+// Get two messages            
+$messages = $queues->receiveMessages($queueId, 2);
+]]></programlisting>
+        </example>
+
+        <para>
+            When a message is received, it is not visible to other clients. It is not deleted from
+            the queue, however, until the client that has received the message calls the
+            <methodname>deleteMessage()</methodname> method. If it is not deleted during the
+            specfied visibility timeout, it will become visible to all other clients again. In other
+            words, all clients will be able to retrieve the message with the
+            <methodname>receiveMessages()</methodname> method if the visibility timeout is exceeded.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.delete">
+        <title>Delete a message</title>
+
+        <para>
+            In order to delete the message from the queue, use the
+            <methodname>deleteMessage()</methodname> method.  This method deletes the specified
+            message.
+        </para>
+
+        <example id="zend.cloud.queueservice.delete.example">
+              <title>Deleting a message</title>
+
+            <programlisting language="php"><![CDATA[
+// process and delete $max messages            
+$messages = $queues->receiveMessages($queueId, $max);
+if (count($messages)) {
+    foreach ($messages as $message) {
+        process($message);
+        $queues->deleteMessage($queueId, $message);
+    }
+}
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.queueservice.concreteadapters">
+        <title>Accessing concrete adapters</title>
+
+        <para>
+            Sometimes it is necessary to retrieve the concrete adapter for the service that the
+            Queue API is working with. This can be achieved by using the
+            <methodname>getAdapter()</methodname> method.
+        </para>
+
+        <note>
+            <para>
+                Accessing the underlying adapter breaks portability among services, so it should be
+                reserved for exceptional circumstances only.
+            </para>
+        </note>
+
+        <example id="zend.cloud.queueservice.concreteadapters.example">
+            <title>Using a concrete adapter</title>
+
+            <programlisting language="php"><![CDATA[
+// send the message directly with the SQS client library
+$sqs = $queues->getAdapter();
+$sqs->sendMessage("myQueue", "hello!");
+]]></programlisting>
+        </example>
+    </sect2>
+</sect1>

+ 717 - 0
documentation/manual/en/module_specs/Zend_Cloud_StorageService.xml

@@ -0,0 +1,717 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.cloud.storageservice">
+    <title>StorageService Introduction</title>
+
+    <para>
+        The storage service in the Simple Cloud API implements a basic interface for file storage on
+        the cloud. The files have no internal structure as far as the service is concerned, and are
+        identified by a string key that is analogous to a filepath on a filesystem.  
+    </para>
+
+    <sect2 id="zend.cloud.storageservice.adapters">
+        <title>StorageService Adapters</title>
+
+        <para>
+            The interface <classname>Zend_Cloud_StorageService_Adapter</classname> defines methods
+            that each concrete storage service adapter must implement.  The following adapters are
+            shipped with the Simple Cloud API:  
+        </para>
+        
+        <itemizedlist>
+            <listitem>
+                <ulink url="http://aws.amazon.com/s3/"><classname>Zend_Cloud_StorageService_Adapter_S3</classname></ulink>
+            </listitem>
+
+            <listitem>
+                <ulink url="http://msdn.microsoft.com/en-us/library/dd179423.aspx"><classname>Zend_Cloud_StorageService_Adapter_WindowsAzure</classname></ulink>
+            </listitem>
+
+            <listitem>
+                <ulink url="http://developer.nirvanix.com/sitefiles/1000/API.html"><classname>Zend_Cloud_StorageService_Adapter_Nirvanix</classname></ulink>
+            </listitem>
+
+            <listitem>
+                <classname>Zend_Cloud_StorageService_Adapter_FileSystem</classname>
+            </listitem>
+        </itemizedlist>
+        
+        <para>
+            To create the service object, call the static method
+            <methodname>Zend_Cloud_StorageService_Factory::getAdapter()</methodname>, which accepts
+            either an array or a <classname>Zend_Config</classname> object.  The key named
+            <varname>storage_adapter</varname> should specify the concrete adapter class.
+            Adapter-specific keys may also be passed in this configuration parameter.
+        </para>
+        
+        <example id="zend.cloud.storageservice.factory.example">
+            <title>Using the StorageService Factory</title>
+
+            <programlisting language="php"><![CDATA[
+$storage = Zend_Cloud_StorageService_Factory::getAdapter(array(
+    Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY => 'Zend_Cloud_StorageService_Adapter_S3',
+    Zend_Cloud_StorageService_Adapter_S3::AWS_ACCESS_KEY   => $amazonKey,
+    Zend_Cloud_StorageService_Adapter_S3::AWS_SECRET_KEY   => $amazonSecret,
+));
+]]></programlisting>
+        </example>
+        
+        <sect3 id="zend.cloud.storageservice.adapters.options">
+            <title>StorageService Adapter Options</title>
+
+            <table frame='all' id="zend.cloud.storageservice.options.s3">
+                <title>Zend_Cloud_StorageService_Adapter_S3 options</title>
+
+                <tgroup cols="4">
+                    <thead>
+                        <row>
+                            <entry>Option key</entry>
+                            <entry>Description</entry>
+                            <entry>Used in</entry>
+                            <entry>Required</entry>
+                            <entry>Default</entry>
+                        </row>
+                    </thead>
+
+                    <tbody>
+                        <row>
+                            <entry>aws_accesskey</entry>
+                            <entry>Amazon AWS access key</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>aws_secretkey</entry>
+                            <entry>Amazon AWS secret key</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+                        
+                        <row>
+                            <entry>bucket_name</entry>
+                            <entry>The name of the S3 bucket for this item</entry>
+                            <entry>
+                                Used in the constructor to set the default bucket for the
+                                instantiated service. This option can also be specified in any of
+                                the item access operations.
+                            </entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+                        
+                        <row>
+                            <entry>bucket_as_domain</entry>
+                            <entry>
+                                Indicates that the bucket name is part of the domain name
+                            </entry>
+                            <entry>
+                                Used in constructor to set the default behavior for the instantiated
+                                service. This option can also be specified in any of the item access
+                                operations.
+                            </entry>
+                            <entry>No</entry>
+                            <entry>False</entry>
+                        </row>
+
+                        <row>
+                            <entry>metadata</entry>
+                            <entry>Array of metadata to associate with the item</entry>
+                            <entry><methodname>storeItem()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>fetch_stream</entry>
+                            <entry>
+                                Indicates whether the response is stream, and not a string
+                                <note>
+                                    <para>
+                                        See the <classname>Zend_Service_Amazon_S3</classname>
+                                        documentation for more about handling streamed responses)
+                                    </para>
+                                </note>
+                            </entry>
+                            <entry><methodname>fetchItem()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>False</entry>
+                        </row>
+
+                        <row>
+                            <entry>http_adapter</entry>
+                            <entry>HTTP adapter to use in all access operations</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry><classname>Zend_Http_Client_Adapter_Socket</classname></entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </table>
+
+            <table frame='all' id="zend.cloud.storageservice.options.azure">
+                <title>Zend_Cloud_StorageService_Adapter_WindowsAzure options</title>
+                <tgroup cols="4">
+                    <thead>
+                        <row>
+                            <entry>Option key</entry>
+                            <entry>Description</entry>
+                            <entry>Used in</entry>
+                            <entry>Required</entry>
+                            <entry>Default</entry>
+                        </row>
+                    </thead>
+
+                    <tbody>
+                        <row>
+                            <entry>storage_accountname</entry>
+                            <entry>Windows Azure account name</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_accountkey</entry>
+                            <entry>Windows Azure account key</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_container</entry>
+                            <entry>Container to use for this storage object</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_host</entry>
+                            <entry>Windows Azure access host</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry><varname>blob.core.windows.net</varname></entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_proxy_host</entry>
+                            <entry>Proxy hostname</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_proxy_port</entry>
+                            <entry>Proxy port</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry>8080</entry>
+                        </row>
+
+                        <row>
+                            <entry>storage_proxy_credentials</entry>
+                            <entry>Proxy credentials</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>http_adapter</entry>
+                            <entry>HTTP adapter to use in all access operations</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry><classname>Zend_Http_Client_Adapter_Socket</classname></entry>
+                        </row>
+
+                        <row>
+                            <entry>returntype</entry>
+                            <entry>
+                                How to return the results.
+                                <itemizedlist>
+                                    <listitem>
+                                        <para>For <methodname>fetchItem()</methodname>:</para>
+
+                                        <variablelist>
+                                            <varlistentry>
+                                                <term><constant>RETURN_STRING</constant></term>
+
+                                                <listitem><para>
+                                                        Return the data as strings.
+                                                </para></listitem>
+                                            </varlistentry>
+
+                                            <varlistentry>
+                                                <term><constant>RETURN_PATH</constant></term>
+
+                                                <listitem>
+                                                    <para>
+                                                        save data on disk in temp file, return path
+                                                        name
+                                                    </para>
+                                                </listitem> 
+                                            </varlistentry>
+
+                                            <varlistentry>
+                                                <term><constant>RETURN_STREAM</constant></term>
+
+                                                <listitem>
+                                                    <para>Default: Return the data as stream</para>
+                                                </listitem> 
+                                            </varlistentry>
+                                        </variablelist>
+                                    </listitem>
+
+                                    <listitem>
+                                        <para>For <methodname>listItems()</methodname>:</para>
+
+                                        <variablelist>
+                                            <varlistentry>
+                                                <term><constant>RETURN_NAMES</constant></term>
+
+                                                <listitem>
+                                                    <para>return the list of item names (default)</para>
+                                                </listitem> 
+                                            </varlistentry>
+
+                                            <varlistentry>
+                                                <term><constant>RETURN_LIST</constant></term>
+
+                                                <listitem>
+                                                    <para>return the list of WindowsAzure objects</para>
+                                                </listitem> 
+                                            </varlistentry>
+                                        </variablelist>
+                                    </listitem>
+                                </itemizedlist>
+                            </entry>
+                            <entry>
+                                <methodname>fetchItem()</methodname>, 
+                                <methodname>listItems()</methodname>
+                            </entry>
+                            <entry>No</entry>
+                            <entry>
+                                <constant>RETURN_STREAM</constant> for
+                                <methodname>fetchItem()</methodname>;
+                                <constant>RETURN_NAMES</constant> for
+                                <methodname>listItems()</methodname>
+                            </entry>
+                        </row>
+
+                        <row>
+                            <entry>return_path</entry>
+                            <entry>
+                                Return path. This is the URL that can be used to access the item
+                                after it has been uploaded.
+                            </entry>
+                            <entry>Path used to save data</entry>
+                            <entry><methodname>fetchItem()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>System tmp directory</entry>
+                        </row>
+
+                        <row>
+                            <entry>return_openmode</entry>
+                            <entry>
+                                <methodname>fopen()</methodname> mode used to open the file for
+                                saving data
+                            </entry>
+                            <entry><methodname>fetchItem()</methodname></entry>
+                            <entry>No</entry>
+                            <entry>'r'</entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </table>
+
+            <table frame='all' id="zend.cloud.storageservice.options.nirvanix">
+                <title>Zend_Cloud_StorageService_Adapter_Nirvanix options</title>
+
+                <tgroup cols="4">
+                    <thead>
+                        <row>
+                            <entry>Option key</entry>
+                            <entry>Description</entry>
+                            <entry>Used in</entry>
+                            <entry>Required</entry>
+                            <entry>Default</entry>
+                        </row>
+                    </thead>
+
+                    <tbody>
+                        <row>
+                            <entry>auth_username</entry>
+                            <entry>Nirvanix user name</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>auth_password</entry>
+                            <entry>Nirvanix password</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>auth_accesskey</entry>
+                            <entry>Nirvanix access key</entry>
+                            <entry>Constructor</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+
+                        <row>
+                            <entry>http_adapter</entry>
+                            <entry>HTTP adapter to use in all access operations</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry><classname>Zend_Http_Client_Adapter_Socket</classname></entry>
+                        </row>
+
+                        <row>
+                            <entry>remote_directory</entry>
+                            <entry>Nirvanix directory to use</entry>
+                            <entry>Constructor (mandatory)</entry>
+                            <entry>Yes</entry>
+                            <entry>None</entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </table>
+
+            <table frame='all' id="zend.cloud.storageservice.options.filesystem">
+                <title>Zend_Cloud_StorageService_Adapter_Filesystem options</title>
+
+                <tgroup cols="4">
+                    <thead>
+                        <row>
+                            <entry>Option key</entry>
+                            <entry>Description</entry>
+                            <entry>Used in</entry>
+                            <entry>Required</entry>
+                            <entry>Default</entry>
+                        </row>
+                    </thead>
+
+                    <tbody>
+                        <row>
+                            <entry>local_directory</entry>
+                            <entry>Local directory where the files will be stored</entry>
+                            <entry>Constructor</entry>
+                            <entry>No</entry>
+                            <entry>System tmp directory</entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </table>
+        </sect3>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.concepts">
+        <title>Basic concepts</title>
+
+        <para>
+            Different cloud storage services use their own unique terminology to refer to document
+            storage concepts. The SimpleCloud API defines a number of common concepts that are
+            shared among all major providers.
+        </para>
+
+        <para>
+            The storage service identifies files by string keys, which may be URL paths or another
+            service-specific identifier. The items can be stored and retrieved using this key. Each
+            item can have <emphasis>metadata</emphasis> associated with it. These metadata carry
+            service-specific information about the item, such as size, type, permissions, etc. as
+            defined in the adapter for that provider.
+        </para>
+    </sect2>
+    
+    <sect2 id="zend.cloud.storageservice.exceptions">
+        <title>Exceptions</title>
+
+        <para>
+            If some error occurs inside the storage service, a
+            <classname>Zend_Cloud_StorageService_Exception</classname> is thrown. If the exception
+            was caused by underlying service driver, you can use the
+            <methodname>getClientException()</methodname> method to retrieve the original exception. 
+        </para>
+
+        <para>
+            Since different cloud providers implement different sets of services, some adapters do
+            not implement certain features. In this case, the
+            <classname>Zend_Cloud_OperationNotAvailableException</classname> exception is thrown.
+        </para>
+    </sect2>
+    
+    <sect2 id="zend.cloud.storageservice.store">
+        <title>Store an item</title>
+
+        <para>
+            <methodname>storeItem()</methodname> method is used to upload or otherwise add files to
+            the storage provider.
+        </para>
+
+        <example id="zend.cloud.storageservice.store.example">
+            <title>Storing an item</title>
+
+            <programlisting language="php"><![CDATA[
+$data = file_get_contents('/my/local/dir/picture.jpg');
+$returnedData = $storage->storeItem('/my/remote/path/picture.jpg', $data);
+]]></programlisting>
+        </example>
+
+        <para>
+            An optional third parameter describes service-specific options.
+        </para>
+
+        <example id="zend.cloud.storageservice.store-options.example">
+            <title>Storing an item with options</title>
+
+            <programlisting language="php"><![CDATA[
+$data = file_get_contents("/my/local/dir/picture.jpg");
+
+// Use S3 bucket: myBucket
+// Make this item publicly readable
+$returnedData = $storage->storeItem(
+    '/my/remote/path/picture.jpg', 
+    $data, 
+    array(
+        Zend_Cloud_StorageService_Adapter_S3::BUCKET_NAME => "myBucket",
+        Zend_Cloud_StorageService_Adapter_S3::METADATA    => array(
+            Zend_Service_Amazon_S3::S3_ACL_HEADER => Zend_Service_Amazon_S3::S3_ACL_PUBLIC_READ,
+        )
+    )
+);
+]]></programlisting>
+        </example>
+
+        <para>
+            For service adapters that support streaming, data can also be a PHP stream (i.e. opened
+            file).
+        </para>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.fetch">
+        <title>Fetch an item</title>
+
+        <para>
+            The <methodname>fetchItem()</methodname> operation retrieves an item from the storage.
+        </para>
+
+        <example id="zend.cloud.storageservice.fetch.example">
+            <title>Fetching an item</title>
+
+            <programlisting language="php"><![CDATA[
+$returnedData = $storage->fetchItem("/my/remote/path/picture.jpg");
+file_put_contents($localFilePath, $returnedData);
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.delete">
+        <title>Delete an item</title>
+
+        <para>
+            The <methodname>deleteItem()</methodname> operation removes an item from the storage
+            service.
+        </para>
+
+        <example id="zend.cloud.storageservice.delete.example">
+            <title>Deleting an item</title>
+
+            <programlisting language="php"><![CDATA[
+$storage->deleteItem("/my/remote/path/picture.jpg");           
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.copy">
+        <title>Copy an item</title>
+
+        <para>
+            The <methodname>copyItem()</methodname> operation creates a copy of the item in the
+            storage.
+        </para>
+
+        <note>
+            <para>
+                Not all services support copying natively. If this is the case, the adapter will
+                simulate the operation, fetching the item and storing it under the target path.
+            </para>
+        </note>
+
+        <example id="zend.cloud.storageservice.copy.example">
+            <title>Copying an item</title>
+            <programlisting language="php"><![CDATA[
+$storage->copyItem(
+    '/my/remote/path/picture.jpg', 
+    '/anothor/remote/dir/picturecopy.jpg'
+);
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.move">
+        <title>Move an item</title>
+
+        <para>
+            The <methodname>moveItem()</methodname> operation moves an item from one key (or
+            directory) to another.
+        </para> 
+
+        <note>
+            <para>
+                Not all services support moving natively. If this is the case the adapter will
+                simulate the operation, fetching the item, storing it under the target path, then
+                deleting the original file.
+            </para>
+        </note>
+
+        <example id="zend.cloud.storageservice.move.example">
+            <title>Moving an item</title>
+            <programlisting language="php"><![CDATA[
+$storage->moveItem(
+    '/my/remote/path/picture.jpg',
+    '/anothor/remote/dir/newpicture.jpg'
+);
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.rename">
+        <title>Rename an item</title>
+
+        <para>
+            The <methodname>renameItem()</methodname> operation changes the item name. For some
+            services, this operation may be equivalent to moving to its original directory with a
+            new name. 
+        </para>
+
+        <example id="zend.cloud.storageservice.rename.example">
+            <title>Renaming an item</title>
+
+            <programlisting language="php"><![CDATA[
+$storage->renameItem('/my/remote/path/picture.jpg', 'newpicture.jpg');                    
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.list">
+        <title>List items</title>
+
+        <para>
+            To list the items stored in the specified path, use the
+            <methodname>listItems()</methodname> method. The method returns a list of names
+            identifying matching remote items. 
+        </para>
+
+        <example id="zend.cloud.storageservice.list.example">
+            <title>List items</title>
+            <programlisting language="php"><![CDATA[
+$objects = $storage->listItems('/my/remote/path/');
+foreach ($objects as $objname) {
+    echo "Found: $objname\n";
+}            
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.fetch-metadata">
+        <title>Fetching metadata</title>
+
+        <para>
+            Some services store a set of key-value pairs along with the item as metadata.  Use the
+            <methodname>fetchMetadata()</methodname> method to retrieve an item's metadata.
+        </para>
+
+        <example id="zend.cloud.storageservice.fetch-metadata.example">
+            <title>Fetching metadata</title>
+
+            <programlisting language="php"><![CDATA[
+$data = $storage->fetchMetadata('/my/remote/path/picture.jpg');
+foreach ($data as $key => $value) {
+    echo "Metadata $key: $value\n";
+}            
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.store-metadata">
+        <title>Store metadata</title>
+
+        <para>
+            Depending on the service, metadata can be supplied either when storing the item or with
+            a separate request. In the latter case, use <methodname>storeMetadata()</methodname> to
+            add or update this metadata.
+        </para>
+
+        <example id="zend.cloud.storageservice.store-metadata.example">
+            <title>Storing metadata</title>
+
+            <programlisting language="php"><![CDATA[
+$data = $storage->storeMetadata('/my/remote/path/picture.jpg', array(
+    'type'     => 'JPEG',
+    'category' => 'Portrait',
+));
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.delete-metadata">
+        <title>Delete metadata</title>
+
+        <para>
+            The <methodname>deleteMetadata()</methodname> method removes all user-supplied metadata
+            from an item.
+        </para>
+
+        <note>
+            <para>
+                Not all services support removing metadata.
+            </para>
+        </note>
+
+        <example id="zend.cloud.storageservice.delete-metadata.example">
+            <title>Deleting metadata</title>
+
+            <programlisting language="php"><![CDATA[
+$storage->deleteMetadata("/my/remote/path/picture.jpg");            
+]]></programlisting>
+        </example>
+    </sect2>
+
+    <sect2 id="zend.cloud.storageservice.adapter">
+        <title>Accessing concrete adapters</title>
+
+        <para>
+            Sometimes it is necessary to retrieve the concrete adapter for the service that the
+            Storage API is working with. This can be achieved by using the
+            <methodname>getAdapter()</methodname> method.
+        </para>
+
+        <note>
+            <para>
+                Accessing the underlying adapter breaks portability among services, so it should be
+                reserved for exceptional circumstances only.
+            </para>
+        </note>
+
+        <example id="zend.cloud.storageservice.adapter.example">
+            <title>Using a concrete adapter</title>
+
+            <programlisting language="php"><![CDATA[
+// the Simple Cloud Storage API doesn't support "clean bucket" operation
+// the concrete adapter can be used to access this feature 
+$s3 = $storage->getClient();
+$s3->cleanBucket("oldBucket");
+]]></programlisting>
+        </example>
+    </sect2>
+</sect1>

+ 67 - 0
library/Zend/Cloud/AbstractFactory.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Abstract factory for Zend_Cloud resources
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_AbstractFactory
+{
+    /**
+     * Constructor
+     * 
+     * @return void
+     */
+    private function __construct()
+    {
+        // private ctor - should not be used
+    }
+    
+    /**
+     * Get an individual adapter instance
+     * 
+     * @param  string $adapterOption 
+     * @param  array|Zend_Config $options 
+     * @return null|Zend_Cloud_DocumentService_Adapter|Zend_Cloud_QueueService_Adapter|Zend_Cloud_StorageService_Adapter
+     */
+    protected static function _getAdapter($adapterOption, $options) 
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!isset($options[$adapterOption])) {
+            return null;
+        }
+
+        $classname = $options[$adapterOption];
+        unset($options[$adapterOption]);
+        if (!class_exists($classname)) {
+            require_once 'Zend/Loader.php';
+            Zend_Loader::loadClass($classname);
+        }
+
+        return new $classname($options);
+    }
+}

+ 155 - 0
library/Zend/Cloud/DocumentService/Adapter.php

@@ -0,0 +1,155 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Common interface for document storage services in the cloud. This interface
+ * supports most document services and provides some flexibility for
+ * vendor-specific features and requirements via an optional $options array in
+ * each method signature. Classes implementing this interface should implement
+ * URI construction for collections and documents from the parameters given in each
+ * method and the account data passed in to the constructor. Classes
+ * implementing this interface are also responsible for security; access control
+ * isn't currently supported in this interface, although we are considering
+ * access control support in future versions of the interface.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+interface Zend_Cloud_DocumentService_Adapter
+{
+    // HTTP adapter to use for connections
+    const HTTP_ADAPTER = 'http_adapter';
+
+    /**
+     * Create collection.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return array
+     */
+    public function createCollection($name, $options = null);
+
+    /**
+     * Delete collection.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return void
+     */
+    public function deleteCollection($name, $options = null);
+
+       /**
+     * List collections.
+     *
+     * @param  array  $options
+     * @return array List of collection names
+     */
+    public function listCollections($options = null);
+
+    /**
+     * List all documents in a collection
+     * 
+     * @param  string $collectionName 
+     * @param  null|array $options 
+     * @return Zend_Cloud_DocumentService_DocumentSet
+     */
+    public function listDocuments($collectionName, array $options = null);
+
+    /**
+     * Insert document
+     *
+     * @param  string $collectionName Collection name
+     * @param  Zend_Cloud_DocumentService_Document $document Document to insert
+     * @param  array $options
+     * @return boolean
+     */
+    public function insertDocument($collectionName, $document, $options = null);
+
+    /**
+     * Replace document
+     * The new document replaces the existing document with the same ID.
+     * 
+     * @param string $collectionName Collection name
+     * @param Zend_Cloud_DocumentService_Document $document
+     * @param array $options
+     */
+    public function replaceDocument($collectionName, $document, $options = null);
+
+    /**
+     * Update document
+     * The fields of the existing documents will be updated. 
+     * Fields not specified in the set will be left as-is. 
+     *
+     * @param  string $collectionName
+     * @param  mixed|Zend_Cloud_DocumentService_Document $documentID Document ID, adapter-dependent, or document containing updates
+     * @param  array|Zend_Cloud_DocumentService_Document $fieldset Set of fields to update
+     * @param  array                   $options
+     * @return boolean
+     */
+    public function updateDocument($collectionName, $documentID, $fieldset = null, $options = null);
+    
+    /**
+     * Delete document
+     *
+     * @param string $collectionName Collection name
+     * @param mixed  $documentID Document ID, adapter-dependent
+     * @param array  $options
+     * @return void
+     */
+    public function deleteDocument($collectionName, $documentID, $options = null);
+
+    /**
+     * Fetch single document by ID
+     * 
+     * Will return false if the document does not exist
+     * 
+     * @param string $collectionName Collection name
+     * @param mixed $documentID Document ID, adapter-dependent
+     * @param array $options
+     * @return Zend_Cloud_DocumentService_Document
+     */
+    public function fetchDocument($collectionName, $documentID, $options = null);
+    
+    /**
+     * Query for documents stored in the document service. If a string is passed in
+     * $query, the query string will be passed directly to the service.
+     *
+     * @param  string $collectionName Collection name
+     * @param  string $query
+     * @param  array $options
+     * @return array Array of field sets
+     */
+    public function query($collectionName, $query, $options = null);
+    
+    /**
+     * Create query statement
+     * 
+     * @param string $fields
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function select($fields = null);
+    
+    /**
+     * Get the concrete service client
+     */
+    public function getClient();
+}

+ 130 - 0
library/Zend/Cloud/DocumentService/Adapter/AbstractAdapter.php

@@ -0,0 +1,130 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/DocumentService/Adapter.php';
+require_once 'Zend/Cloud/DocumentService/Document.php';
+require_once 'Zend/Cloud/DocumentService/DocumentSet.php';
+require_once 'Zend/Cloud/DocumentService/Query.php';
+
+/**
+ * Abstract document service adapter
+ *
+ * Provides functionality surrounding setting classes for each of:
+ * - document objects
+ * - document set objects
+ * - query class objects
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Cloud_DocumentService_Adapter_AbstractAdapter 
+    implements Zend_Cloud_DocumentService_Adapter
+{
+    const DOCUMENT_CLASS    = 'document_class';
+    const DOCUMENTSET_CLASS = 'documentset_class';
+    const QUERY_CLASS       = 'query_class';
+
+    /**
+     * Class to utilize for new document objects
+     * @var string
+     */
+    protected $_documentClass = 'Zend_Cloud_DocumentService_Document';
+
+    /**
+     * Class to utilize for new document set objects
+     * @var string
+     */
+    protected $_documentSetClass = 'Zend_Cloud_DocumentService_DocumentSet';
+
+    /**
+     * Class to utilize for new query objects
+     * 
+     * @var string
+     */
+    protected $_queryClass = 'Zend_Cloud_DocumentService_Query';
+
+    /**
+     * Set the class for document objects
+     * 
+     * @param  string $class 
+     * @return Zend_Cloud_DocumentService_Adapter_AbstractAdapter
+     */
+    public function setDocumentClass($class)
+    {
+        $this->_documentClass = (string) $class;
+        return $this;
+    }
+
+    /**
+     * Get the class for document objects
+     * 
+     * @return string
+     */
+    public function getDocumentClass()
+    {
+        return $this->_documentClass;
+    }
+
+    /**
+     * Set the class for document set objects
+     * 
+     * @param  string $class 
+     * @return Zend_Cloud_DocumentService_Adapter_AbstractAdapter
+     */
+    public function setDocumentSetClass($class)
+    {
+        $this->_documentSetClass = (string) $class;
+        return $this;
+    }
+
+    /**
+     * Get the class for document set objects
+     * 
+     * @return string
+     */
+    public function getDocumentSetClass()
+    {
+        return $this->_documentSetClass;
+    }
+
+    /**
+     * Set the query class for query objects
+     * 
+     * @param  string $class 
+     * @return Zend_Cloud_DocumentService_Adapter_AbstractAdapter
+     */
+    public function setQueryClass($class)
+    {
+        $this->_queryClass = (string) $class;
+        return $this;
+    }
+
+    /**
+     * Get the class for query objects
+     * 
+     * @return string
+     */
+    public function getQueryClass()
+    {
+        return $this->_queryClass;
+    }
+}

+ 468 - 0
library/Zend/Cloud/DocumentService/Adapter/SimpleDb.php

@@ -0,0 +1,468 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/DocumentService/Adapter/AbstractAdapter.php';
+require_once 'Zend/Cloud/DocumentService/Adapter/SimpleDb/Query.php';
+require_once 'Zend/Cloud/DocumentService/Exception.php';
+require_once 'Zend/Service/Amazon/SimpleDb.php';
+require_once 'Zend/Service/Amazon/SimpleDb/Attribute.php';
+
+/**
+ * SimpleDB adapter for document service.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Adapter_SimpleDb 
+    extends Zend_Cloud_DocumentService_Adapter_AbstractAdapter
+{
+    /*
+     * Options array keys for the SimpleDB adapter.
+     */
+    const AWS_ACCESS_KEY   = 'aws_accesskey';
+    const AWS_SECRET_KEY   = 'aws_secretkey';
+    
+    const ITEM_NAME        = 'ItemName';
+    
+    const MERGE_OPTION     = "merge";
+    const RETURN_DOCUMENTS = "return_documents";
+
+    const DEFAULT_QUERY_CLASS = 'Zend_Cloud_DocumentService_Adapter_SimpleDb_Query';
+
+
+    /**
+     * SQS service instance.
+     * @var Zend_Service_Amazon_SimpleDb
+     */
+    protected $_simpleDb;
+    
+    /**
+     * Class to utilize for new query objects
+     * @var string
+     */
+    protected $_queryClass = 'Zend_Cloud_DocumentService_Adapter_SimpleDb_Query';
+    
+    /**
+     * Constructor
+     * 
+     * @param  array|Zend_Config $options 
+     * @return void
+     */
+    public function __construct($options = array()) 
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid options provided to constructor');
+        }
+
+        $this->_simpleDb = new Zend_Service_Amazon_SimpleDb(
+            $options[self::AWS_ACCESS_KEY], $options[self::AWS_SECRET_KEY]
+        );
+
+        if (isset($options[self::HTTP_ADAPTER])) {
+            $this->_sqs->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]);
+        } 
+
+        if (isset($options[self::DOCUMENT_CLASS])) {
+            $this->setDocumentClass($options[self::DOCUMENT_CLASS]);
+        }
+
+        if (isset($options[self::DOCUMENTSET_CLASS])) {
+            $this->setDocumentSetClass($options[self::DOCUMENTSET_CLASS]);
+        }
+
+        if (isset($options[self::QUERY_CLASS])) {
+            $this->setQueryClass($options[self::QUERY_CLASS]);
+        }
+    }
+
+    /**
+     * Create collection.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return void
+     */
+    public function createCollection($name, $options = null) 
+    {
+        try {
+            $this->_simpleDb->createDomain($name);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on domain creation: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Delete collection.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return void
+     */
+    public function deleteCollection($name, $options = null) 
+    {
+        try {
+            $this->_simpleDb->deleteDomain($name);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on collection deletion: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * List collections.
+     *
+     * @param  array  $options
+     * @return array
+     */
+    public function listCollections($options = null) 
+    {
+        try {
+            // TODO package this in Pages
+            $domains = $this->_simpleDb->listDomains()->getData();
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on collection deletion: '.$e->getMessage(), $e->getCode(), $e);
+        }
+
+        return $domains;
+    }
+
+    /**
+     * List documents
+     *
+     * Returns a key/value array of document names to document objects.
+     *
+     * @param  string $collectionName Name of collection for which to list documents
+     * @param  array|null $options
+     * @return Zend_Cloud_DocumentService_DocumentSet
+     */
+    public function listDocuments($collectionName, array $options = null) 
+    {
+        $query = $this->select('*')->from($collectionName);
+        $items = $this->query($collectionName, $query, $options);
+        return $items;
+    }
+
+    /**
+     * Insert document
+     *
+     * @param  string $collectionName Collection into which to insert document
+     * @param  array|Zend_Cloud_DocumentService_Document $document
+     * @param  array $options
+     * @return void
+     */
+    public function insertDocument($collectionName, $document, $options = null)
+    {
+        if (is_array($document)) {
+            $document =  $this->_getDocumentFromArray($document);
+        } 
+        
+        if (!$document instanceof Zend_Cloud_DocumentService_Document) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied');
+        }
+        
+        try {
+            $this->_simpleDb->putAttributes(
+                $collectionName,
+                $document->getID(),
+                $this->_makeAttributes($document->getID(), $document->getFields())
+            );
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document insertion: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Replace an existing document with a new version
+     * 
+     * @param  string $collectionName 
+     * @param  array|Zend_Cloud_DocumentService_Document $document
+     * @param  array $options 
+     * @return void
+     */
+    public function replaceDocument($collectionName, $document, $options = null)
+    {
+        if (is_array($document)) {
+            $document =  $this->_getDocumentFromArray($document);
+        } 
+        
+        if (!$document instanceof Zend_Cloud_DocumentService_Document) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied');
+        }
+ 
+        // Delete document first, then insert. PutAttributes always keeps any
+        // fields not referenced in the payload, but present in the document
+        $documentId = $document->getId();
+        $fields     = $document->getFields();
+        $docClass   = get_class($document);
+        $this->deleteDocument($collectionName, $document, $options);
+
+        $document   = new $docClass($fields, $documentId);
+        $this->insertDocument($collectionName, $document);
+    }
+    
+    /**
+     * Update document. The new document replaces the existing document.
+     *
+     * Option 'merge' specifies to add all attributes (if true) or
+     * specific attributes ("attr" => true) instead of replacing them.
+     * By default, attributes are replaced.   
+     * 
+     * @param  string $collectionName
+     * @param  mixed|Zend_Cloud_DocumentService_Document $documentId Document ID, adapter-dependent
+     * @param  array|Zend_Cloud_DocumentService_Document $fieldset Set of fields to update
+     * @param  array                   $options
+     * @return boolean
+     */
+    public function updateDocument($collectionName, $documentId, $fieldset = null, $options = null)
+    {
+        if (null === $fieldset && $documentId instanceof Zend_Cloud_DocumentService_Document) {
+            $fieldset   = $documentId->getFields();
+            if (empty($documentId)) {
+                $documentId = $documentId->getId();
+            }
+        } elseif ($fieldset instanceof Zend_Cloud_DocumentService_Document) {
+            if (empty($documentId)) {
+                $documentId = $fieldset->getId();
+            }
+            $fieldset = $fieldset->getFields();
+        }
+        
+        $replace = array();
+        if (empty($options[self::MERGE_OPTION])) {
+            // no merge option - we replace all
+            foreach ($fieldset as $key => $value) {
+                $replace[$key] = true;
+            }
+        } elseif (is_array($options[self::MERGE_OPTION])) {
+            foreach ($fieldset as $key => $value) {
+                if (empty($options[self::MERGE_OPTION][$key])) {
+                    // if there's merge key, we add it, otherwise we replace it
+                    $replace[$key] = true;
+                }
+            }
+        } // otherwise $replace is empty - all is merged
+        
+        try {
+            $this->_simpleDb->putAttributes(
+                $collectionName,
+                $documentId,
+                $this->_makeAttributes($documentId, $fieldset),
+                $replace
+            );
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document update: '.$e->getMessage(), $e->getCode(), $e);
+        }
+        return true;
+    }
+
+    /**
+     * Delete document.
+     *
+     * @param  string $collectionName Collection from which to delete document
+     * @param  mixed  $document Document ID or Document object.
+     * @param  array  $options
+     * @return boolean
+     */
+    public function deleteDocument($collectionName, $document, $options = null)
+    {
+        if ($document instanceof Zend_Cloud_DocumentService_Document) {
+            $document = $document->getId();
+        }
+        try {
+            $this->_simpleDb->deleteAttributes($collectionName, $document);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document deletion: '.$e->getMessage(), $e->getCode(), $e);
+        }
+        return true;
+    }
+
+    /**
+     * Fetch single document by ID
+     * 
+     * @param  string $collectionName Collection name
+     * @param  mixed $documentId Document ID, adapter-dependent
+     * @param  array $options
+     * @return Zend_Cloud_DocumentService_Document
+     */
+    public function fetchDocument($collectionName, $documentId, $options = null)
+    {
+        try {
+            $attributes = $this->_simpleDb->getAttributes($collectionName, $documentId);
+            if ($attributes == false || count($attributes) == 0) {
+                return false;
+            }
+            return $this->_resolveAttributes($attributes, true);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on fetching document: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Query for documents stored in the document service. If a string is passed in
+     * $query, the query string will be passed directly to the service.
+     *
+     * @param  string $collectionName Collection name
+     * @param  string $query
+     * @param  array $options
+     * @return array Zend_Cloud_DocumentService_DocumentSet
+     */
+    public function query($collectionName, $query, $options = null)
+    {
+        $returnDocs = isset($options[self::RETURN_DOCUMENTS])
+                    ? (bool) $options[self::RETURN_DOCUMENTS]
+                    : true;
+
+        try {
+            if ($query instanceof Zend_Cloud_DocumentService_Adapter_SimpleDb_Query) {
+                $query = $query->assemble($collectionName);
+            }
+            $result = $this->_simpleDb->select($query);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document query: '.$e->getMessage(), $e->getCode(), $e);
+        }
+
+        return $this->_getDocumentSetFromResultSet($result, $returnDocs);
+    }
+
+    /**
+     * Create query statement
+     * 
+     * @param  string $fields
+     * @return Zend_Cloud_DocumentService_Adapter_SimpleDb_Query
+     */
+    public function select($fields = null)
+    {
+        $queryClass = $this->getQueryClass();
+        if (!class_exists($queryClass)) {
+            require_once 'Zend/Loader.php';
+            Zend_Loader::loadClass($queryClass);
+        }
+
+        $query = new $queryClass($this);
+        $defaultClass = self::DEFAULT_QUERY_CLASS;
+        if (!$query instanceof $defaultClass) {
+            throw new Zend_Cloud_DocumentService_Exception('Query class must extend ' . self::DEFAULT_QUERY_CLASS);
+        }
+
+        $query->select($fields);
+        return $query;        
+    }
+    
+    /**
+     * Get the concrete service client
+     *
+     * @return Zend_Service_Amazon_SimpleDb
+     */
+    public function getClient()
+    {
+        return $this->_simpleDb;
+    }
+    
+    /**
+     * Convert array of key-value pairs to array of Amazon attributes
+     * 
+     * @param string $name
+     * @param array $attributes
+     * @return array
+     */
+    protected function _makeAttributes($name, $attributes)
+    {
+        $result = array();
+        foreach ($attributes as $key => $attr) {
+            $result[] = new Zend_Service_Amazon_SimpleDb_Attribute($name, $key, $attr);
+        }
+        return $result;
+    }
+    
+    /**
+     * Convert array of Amazon attributes to array of key-value pairs 
+     * 
+     * @param array $attributes
+     * @return array
+     */
+    protected function _resolveAttributes($attributes, $returnDocument = false)
+    {
+        $result = array();
+        foreach ($attributes as $attr) {
+            $value = $attr->getValues();
+            if (count($value) == 0) {
+                $value = null;
+            } elseif (count($value) == 1) {
+                $value = $value[0];
+            }
+            $result[$attr->getName()] = $value;
+        }
+
+        // Return as document object?
+        if ($returnDocument) {
+            $documentClass = $this->getDocumentClass();
+            return new $documentClass($result, $attr->getItemName());
+        }
+
+        return $result;
+    }
+    
+    /**
+     * Create suitable document from array of fields
+     * 
+     * @param array $document
+     * @return Zend_Cloud_DocumentService_Document
+     */
+    protected function _getDocumentFromArray($document)
+    {
+        if (!isset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD])) {
+            if (isset($document[self::ITEM_NAME])) {
+                $key = $document[self::ITEM_NAME];
+                unset($document[self::ITEM_NAME]);
+            } else {
+                throw new Zend_Cloud_DocumentService_Exception('Fields array should contain the key field '.Zend_Cloud_DocumentService_Document::KEY_FIELD);
+            }
+        } else {
+            $key = $document[Zend_Cloud_DocumentService_Document::KEY_FIELD];
+            unset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD]);
+        }
+
+        $documentClass = $this->getDocumentClass();
+        return new $documentClass($document, $key);
+    }
+
+    /**
+     * Create a DocumentSet from a SimpleDb resultset
+     * 
+     * @param  Zend_Service_Amazon_SimpleDb_Page $resultSet 
+     * @param  bool $returnDocs 
+     * @return Zend_Cloud_DocumentService_DocumentSet
+     */
+    protected function _getDocumentSetFromResultSet(Zend_Service_Amazon_SimpleDb_Page $resultSet, $returnDocs = true)
+    {
+        $docs = array();
+        foreach ($resultSet->getData() as $item) {
+            $docs[] = $this->_resolveAttributes($item, $returnDocs);
+        }
+
+        $setClass = $this->getDocumentSetClass();
+        return new $setClass($docs);
+    }
+}

+ 175 - 0
library/Zend/Cloud/DocumentService/Adapter/SimpleDb/Query.php

@@ -0,0 +1,175 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/*
+ * @see Zend_Cloud_DocumentService_Query
+ */
+require_once 'Zend/Cloud/DocumentService/Query.php';
+
+/**
+ * Class implementing Query adapter for working with SimpleDb queries in a 
+ * structured way
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Adapter_SimpleDb_Query
+    extends Zend_Cloud_DocumentService_Query
+{
+    /**
+     * @var Zend_Cloud_DocumentService_Adapter_SimpleDb
+     */
+    protected $_adapter;
+
+    /**
+     * Constructor
+     * 
+     * @param  Zend_Cloud_DocumentService_Adapter_SimpleDb $adapter 
+     * @param  null|string $collectionName
+     * @return void
+     */
+    public function __construct(Zend_Cloud_DocumentService_Adapter_SimpleDb $adapter, $collectionName = null)
+    {
+        $this->_adapter = $adapter;
+        if (null !== $collectionName) {
+            $this->from($collectionName);
+        }
+    }
+
+    /**
+     * Get adapter
+     * 
+     * @return Zend_Cloud_DocumentService_Adapter_SimpleDb
+     */
+    public function getAdapter()
+    {
+        return $this->_adapter;
+    }
+
+    /**
+     * Assemble the query into a format the adapter can utilize
+     * 
+     * @var    string $collectionName Name of collection from which to select
+     * @return string
+     */
+    public function assemble($collectionName = null)
+    {
+        $adapter = $this->getAdapter()->getClient();
+        $select  = null;
+        $from    = null;
+        $where   = null;
+        $order   = null;
+        $limit   = null;
+        foreach ($this->getClauses() as $clause) {
+            list($name, $args) = $clause;
+
+            switch ($name) {
+                case self::QUERY_SELECT:
+                    $select = $args[0];
+                    break;
+                case self::QUERY_FROM:
+                    if (null === $from) {
+                        // Only allow setting FROM clause once
+                        $from = $adapter->quoteName($args);
+                    }
+                    break;
+                case self::QUERY_WHERE:
+                    $statement = $this->_parseWhere($args[0], $args[1]);
+                    if (null === $where) {
+                        $where = $statement;
+                    } else {
+                        $operator = empty($args[2]) ? 'AND' : $args[2];
+                        $where .= ' ' . $operator . ' ' . $statement;
+                    }
+                    break;
+                case self::QUERY_WHEREID:
+                    $statement = $this->_parseWhere('ItemName() = ?', array($args));
+                    if (null === $where) {
+                        $where = $statement;
+                    } else {
+                        $operator = empty($args[2]) ? 'AND' : $args[2];
+                        $where .= ' ' . $operator . ' ' . $statement;
+                    }
+                    break;
+                case self::QUERY_ORDER:
+                    $order = $adapter->quoteName($args[0]);
+                    if (isset($args[1])) {
+                        $order .= ' ' . $args[1];
+                    }
+                    break;
+                case self::QUERY_LIMIT:
+                    $limit = $args;
+                    break;
+                default:
+                    // Ignore unknown clauses
+                    break;
+            }
+        }
+
+        if (empty($select)) {
+            $select = "*";
+        }
+        if (empty($from)) {
+            if (null === $collectionName) {
+                require_once 'Zend/Cloud/DocumentService/Exception.php';
+                throw new Zend_Cloud_DocumentService_Exception("Query requires a FROM clause");
+            }
+            $from = $adapter->quoteName($collectionName);
+        }
+        $query = "select $select from $from";
+        if (!empty($where)) {
+            $query .= " where $where";
+        }
+        if (!empty($order)) {
+            $query .= " order by $order";
+        }
+        if (!empty($limit)) {
+            $query .= " limit $limit";
+        }
+        return $query;
+    }
+
+    /**
+     * Parse a where statement into service-specific language
+     * 
+     * @todo   Ensure this fulfills the entire SimpleDB query specification for WHERE
+     * @param  string $where 
+     * @param  array $args 
+     * @return string
+     */
+    protected function _parseWhere($where, $args)
+    {
+        if (!is_array($args)) {
+            $args = (array) $args;
+        }
+        $adapter = $this->getAdapter()->getClient();
+        $i = 0;
+        while (false !== ($pos = strpos($where, '?'))) {
+           $where = substr_replace($where, $adapter->quote($args[$i]), $pos);
+           ++$i;
+        }
+        if (('(' != $where[0]) || (')' != $where[strlen($where) - 1])) {
+            $where = '(' . $where . ')';
+        }
+        return $where;
+    }
+ }

+ 628 - 0
library/Zend/Cloud/DocumentService/Adapter/WindowsAzure.php

@@ -0,0 +1,628 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/DocumentService/Adapter/AbstractAdapter.php';
+require_once 'Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php';
+require_once 'Zend/Cloud/DocumentService/Exception.php';
+require_once 'Zend/Service/WindowsAzure/Storage/DynamicTableEntity.php';
+require_once 'Zend/Service/WindowsAzure/Storage/Table.php';
+
+/**
+ * SimpleDB adapter for document service.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Adapter_WindowsAzure 
+    extends Zend_Cloud_DocumentService_Adapter_AbstractAdapter
+{
+    /*
+     * Options array keys for the Azure adapter.
+     */
+    const ACCOUNT_NAME          = 'storage_accountname';
+    const ACCOUNT_KEY           = 'storage_accountkey';
+    const HOST                  = "storage_host";
+    const PROXY_HOST            = "storage_proxy_host";
+    const PROXY_PORT            = "storage_proxy_port";
+    const PROXY_CREDENTIALS     = "storage_proxy_credentials";
+    const DEFAULT_PARTITION_KEY = "default_partition_key";
+
+    const PARTITION_KEY         = 'PartitionKey';
+    const ROW_KEY               = 'RowKey';
+    const VERIFY_ETAG           = "verify_etag";
+    const TIMESTAMP_KEY         = "Timestamp";
+    
+    const DEFAULT_HOST          = Zend_Service_WindowsAzure_Storage::URL_CLOUD_TABLE;
+    const DEFAULT_QUERY_CLASS   = 'Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query';
+
+    /**
+     * Azure  service instance.
+     * 
+     * @var Zend_Service_WindowsAzure_Storage_Table
+     */
+    protected $_storageClient;
+
+    /**
+     * Class to utilize for new query objects
+     * 
+     * @var string
+     */
+    protected $_queryClass = 'Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query';
+
+    /**
+     * Partition key to use by default when constructing document identifiers
+     * @var string
+     */
+    protected $_defaultPartitionKey;
+
+    /**
+     * Constructor
+     * 
+     * @param array $options 
+     * @return void
+     */
+    public function __construct($options = array()) 
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (empty($options)) {
+            $options = array();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid options provided');
+        }
+
+        if (isset($options[self::DOCUMENT_CLASS])) {
+            $this->setDocumentClass($options[self::DOCUMENT_CLASS]);
+        }
+
+        if (isset($options[self::DOCUMENTSET_CLASS])) {
+            $this->setDocumentSetClass($options[self::DOCUMENTSET_CLASS]);
+        }
+
+        if (isset($options[self::QUERY_CLASS])) {
+            $this->setQueryClass($options[self::QUERY_CLASS]);
+        }
+
+        // Build Zend_Service_WindowsAzure_Storage_Blob instance
+        if (!isset($options[self::HOST])) {
+            $host = self::DEFAULT_HOST;
+        } else {
+            $host = $options[self::HOST];
+        }
+
+        if (! isset($options[self::ACCOUNT_NAME])) {
+            throw new Zend_Cloud_DocumentService_Exception('No Windows Azure account name provided.');
+        }
+
+        if (! isset($options[self::ACCOUNT_KEY])) {
+            throw new Zend_Cloud_DocumentService_Exception('No Windows Azure account key provided.');
+        }
+
+        // TODO: support $usePathStyleUri and $retryPolicy
+        try {
+            $this->_storageClient = new Zend_Service_WindowsAzure_Storage_Table(
+                    $host, $options[self::ACCOUNT_NAME], $options[self::ACCOUNT_KEY]);
+            // Parse other options
+            if (! empty($options[self::PROXY_HOST])) {
+                $proxyHost = $options[self::PROXY_HOST];
+                $proxyPort = isset($options[self::PROXY_PORT]) ? $options[self::PROXY_PORT] : 8080;
+                $proxyCredentials = isset($options[self::PROXY_CREDENTIALS]) ? $options[self::PROXY_CREDENTIALS] : '';
+                $this->_storageClient->setProxy(true, $proxyHost, $proxyPort, $proxyCredentials);
+            }
+            if (isset($options[self::HTTP_ADAPTER])) {
+                $this->_storageClient->setHttpClientChannel($options[self::HTTP_ADAPTER]);
+            }
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document service creation: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Set the default partition key
+     * 
+     * @param  string $key 
+     * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure
+     */
+    public function setDefaultPartitionKey($key)
+    {
+        $this->_validateKey($key);
+        $this->_defaultPartitionKey = $key;
+        return $this;
+    }
+
+    /**
+     * Retrieve default partition key
+     * 
+     * @return null|string
+     */
+    public function getDefaultPartitionKey()
+    {
+        return $this->_defaultPartitionKey;
+    }
+
+    /**
+     * Create collection.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return boolean
+     */
+    public function createCollection($name, $options = null) 
+    {
+        if (!preg_match('/^[A-Za-z][A-Za-z0-9]{2,}$/', $name)) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid collection name; Windows Azure collection names must consist of alphanumeric characters only, and be at least 3 characters long');
+        }
+        try {
+            $this->_storageClient->createTable($name);
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            if (strpos($e->getMessage(), "table specified already exists") === false) {
+                throw new Zend_Cloud_DocumentService_Exception('Error on collection creation: '.$e->getMessage(), $e->getCode(), $e);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Delete collection.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return boolean
+     */
+    public function deleteCollection($name, $options = null) 
+    {
+        try {
+            $this->_storageClient->deleteTable($name);
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            if (strpos($e->getMessage(), "does not exist") === false) {
+                throw new Zend_Cloud_DocumentService_Exception('Error on collection deletion: '.$e->getMessage(), $e->getCode(), $e);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * List collections.
+     *
+     * @param  array  $options
+     * @return array
+     */
+    public function listCollections($options = null) 
+    {
+        try {
+            $tables = $this->_storageClient->listTables();
+            $restables = array();
+            foreach ($tables as $table) {
+                $restables[] = $table->name;
+            }
+            return $restables;
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on collection list: '.$e->getMessage(), $e->getCode(), $e);
+        }
+
+        return $tables;
+    }
+
+    /**
+     * Create suitable document from array of fields
+     * 
+     * @param  array $document
+     * @param  null|string $collectionName Collection to which this document belongs
+     * @return Zend_Cloud_DocumentService_Document
+     */
+    protected function _getDocumentFromArray($document, $collectionName = null)
+    {
+        $key = null;
+        if (!isset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD])) {
+            if (isset($document[self::ROW_KEY])) {
+                $rowKey = $document[self::ROW_KEY];
+                    unset($document[self::ROW_KEY]);
+                if (isset($document[self::PARTITION_KEY])) {
+                    $key = array($document[self::PARTITION_KEY], $rowKey);
+                    unset($document[self::PARTITION_KEY]);
+                } elseif (null !== ($partitionKey = $this->getDefaultPartitionKey())) {
+                    $key = array($partitionKey, $rowKey);
+                } elseif (null !== $collectionName) {
+                    $key = array($collectionName, $rowKey);
+                }
+            }
+        } else {
+            $key = $document[Zend_Cloud_DocumentService_Document::KEY_FIELD];
+            unset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD]);
+        }
+
+        $documentClass = $this->getDocumentClass();
+        return new $documentClass($document, $key);
+    }
+    
+    /**
+     * List all documents in a collection
+     * 
+     * @param  string $collectionName 
+     * @param  null|array $options 
+     * @return Zend_Cloud_DocumentService_DocumentSet
+     */
+    public function listDocuments($collectionName, array $options = null)
+    {
+        $select = $this->select()->from($collectionName);
+        return $this->query($collectionName, $select);
+    }
+
+    /**
+     * Insert document
+     *
+     * @param  array|Zend_Cloud_DocumentService_Document $document
+     * @param  array                         $options
+     * @return boolean
+     */
+    public function insertDocument($collectionName, $document, $options = null)
+    {
+        if (is_array($document)) {
+            $document =  $this->_getDocumentFromArray($document, $collectionName);
+        } 
+        
+        if (!$document instanceof Zend_Cloud_DocumentService_Document) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied');
+        }
+        
+        $key = $this->_validateDocumentId($document->getId(), $collectionName);
+        $document->setId($key);
+        
+        $this->_validateCompositeKey($key);
+        $this->_validateFields($document);
+        try {
+        
+            $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($key[0], $key[1]);
+            $entity->setAzureValues($document->getFields(), true);
+            $this->_storageClient->insertEntity($collectionName, $entity);
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document insertion: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Replace document. 
+     * 
+     * The new document replaces the existing document.
+     *
+     * @param  Zend_Cloud_DocumentService_Document $document
+     * @param  array                         $options
+     * @return boolean
+     */
+    public function replaceDocument($collectionName, $document, $options = null)
+    {
+        if (is_array($document)) {
+            $document = $this->_getDocumentFromArray($document, $collectionName);
+        } 
+        
+        if (!$document instanceof Zend_Cloud_DocumentService_Document) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied');
+        }
+        
+        $key = $this->_validateDocumentId($document->getId(), $collectionName);
+        $this->_validateFields($document);
+        try {
+            $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($key[0], $key[1]);
+            $entity->setAzureValues($document->getFields(), true);
+            if (isset($options[self::VERIFY_ETAG])) {
+                $entity->setEtag($options[self::VERIFY_ETAG]);
+            }
+            
+            $this->_storageClient->updateEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG]));
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document replace: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Update document. 
+     * 
+     * The new document is merged the existing document.
+     *
+     * @param  string $collectionName
+     * @param  mixed|Zend_Cloud_DocumentService_Document $documentId Document identifier or document contaiing updates
+     * @param  null|array|Zend_Cloud_DocumentService_Document Fields to update (or new fields))
+     * @param  array $options
+     * @return boolean
+     */
+    public function updateDocument($collectionName, $documentId, $fieldset = null, $options = null)
+    {
+        if (null === $fieldset && $documentId instanceof Zend_Cloud_DocumentService_Document) {
+            $fieldset   = $documentId->getFields();
+            $documentId = $documentId->getId();
+        } elseif ($fieldset instanceof Zend_Cloud_DocumentService_Document) {
+            if ($documentId == null) {
+                $documentId = $fieldset->getId();
+            }
+            $fieldset = $fieldset->getFields();
+        }
+
+        $this->_validateCompositeKey($documentId, $collectionName);
+        $this->_validateFields($fieldset);
+        try {
+            $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($documentId[0], $documentId[1]);
+
+            // Ensure timestamp is set correctly
+            if (isset($fieldset[self::TIMESTAMP_KEY])) {
+                $entity->setTimestamp($fieldset[self::TIMESTAMP_KEY]);
+                unset($fieldset[self::TIMESTAMP_KEY]);
+            }
+
+            $entity->setAzureValues($fieldset, true);
+            if (isset($options[self::VERIFY_ETAG])) {
+                $entity->setEtag($options[self::VERIFY_ETAG]);
+            }
+            
+            $this->_storageClient->mergeEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG]));
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document update: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Delete document.
+     *
+     * @param  mixed  $document Document ID or Document object.
+     * @param  array  $options
+     * @return void
+     */
+    public function deleteDocument($collectionName, $documentId, $options = null)
+    {
+        if ($documentId instanceof Zend_Cloud_DocumentService_Document) {
+            $documentId = $documentId->getId();
+        }
+
+        $documentId = $this->_validateDocumentId($documentId, $collectionName);
+
+        try {
+            $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($documentId[0], $documentId[1]);
+            if (isset($options[self::VERIFY_ETAG])) {
+                $entity->setEtag($options[self::VERIFY_ETAG]);
+            }
+            $this->_storageClient->deleteEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG]));
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            if (strpos($e->getMessage(), "does not exist") === false) {
+                throw new Zend_Cloud_DocumentService_Exception('Error on document deletion: '.$e->getMessage(), $e->getCode(), $e);
+            }
+        }
+    }
+
+    /**
+     * Fetch single document by ID
+     * 
+     * @param  string $collectionName Collection name
+     * @param  mixed $documentId Document ID, adapter-dependent
+     * @param  array $options
+     * @return Zend_Cloud_DocumentService_Document
+     */
+    public function fetchDocument($collectionName, $documentId, $options = null)
+    {
+        $documentId = $this->_validateDocumentId($documentId, $collectionName);
+        try {
+            $entity = $this->_storageClient->retrieveEntityById($collectionName, $documentId[0], $documentId[1]);
+            $documentClass = $this->getDocumentClass();
+            return new $documentClass($this->_resolveAttributes($entity), array($entity->getPartitionKey(), $entity->getRowKey()));
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            if (strpos($e->getMessage(), "does not exist") !== false) {
+                return false;
+            }
+            throw new Zend_Cloud_DocumentService_Exception('Error on document fetch: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Query for documents stored in the document service. If a string is passed in
+     * $query, the query string will be passed directly to the service.
+     *
+     * @param  string $collectionName Collection name
+     * @param  string|Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query $query
+     * @param  array $options
+     * @return array Zend_Cloud_DocumentService_DocumentSet
+     */
+    public function query($collectionName, $query, $options = null)
+    {
+        try {
+            if ($query instanceof Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query) {
+                $entities = $this->_storageClient->retrieveEntities($query->assemble());
+            } else {
+                $entities = $this->_storageClient->retrieveEntities($collectionName, $query);
+            }
+
+            $documentClass = $this->getDocumentClass();
+            $resultSet     = array();
+            foreach ($entities as $entity) {
+                $resultSet[] = new $documentClass(
+                    $this->_resolveAttributes($entity),
+                    array($entity->getPartitionKey(), $entity->getRowKey())
+                );
+            }
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_DocumentService_Exception('Error on document query: '.$e->getMessage(), $e->getCode(), $e);
+        }
+
+        $setClass = $this->getDocumentSetClass();
+        return new $setClass($resultSet);
+    }
+    
+    /**
+     * Create query statement
+     *
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function select($fields = null)
+    {
+        $queryClass = $this->getQueryClass();
+        if (!class_exists($queryClass)) {
+            require_once 'Zend/Loader.php';
+            Zend_Loader::loadClass($queryClass);
+        }
+
+        $query = new $queryClass();
+        $defaultClass = self::DEFAULT_QUERY_CLASS;
+        if (!$query instanceof $defaultClass) {
+            throw new Zend_Cloud_DocumentService_Exception('Query class must extend ' . self::DEFAULT_QUERY_CLASS);
+        }
+
+        $query->select($fields);
+        return $query;        
+    }
+    
+    /**
+     * Get the concrete service client
+     *
+     * @return Zend_Service_WindowsAzure_Storage_Table
+     */
+    public function getClient()
+    {
+        return $this->_storageClient;
+    }
+    
+    /**
+     * Resolve table values to attributes
+     * 
+     * @param  Zend_Service_WindowsAzure_Storage_TableEntity $entity 
+     * @return array
+     */
+    protected function _resolveAttributes(Zend_Service_WindowsAzure_Storage_TableEntity $entity)
+    {
+        $result = array();
+        foreach ($entity->getAzureValues() as $attr) {
+            $result[$attr->Name] = $attr->Value;
+        }
+        return $result;
+    }
+    
+
+    /**
+     * Validate a partition or row key
+     * 
+     * @param  string $key 
+     * @return void
+     * @throws Zend_Cloud_DocumentService_Exception
+     */
+    protected function _validateKey($key)
+    {
+        if (preg_match('@[/#?' . preg_quote('\\') . ']@', $key)) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid partition or row key provided; must not contain /, \\,  #, or ? characters');
+        }
+    }
+
+    /**
+     * Validate a composite key
+     * 
+     * @param  array $key 
+     * @return throws Zend_Cloud_DocumentService_Exception
+     */
+    protected function _validateCompositeKey(array $key)
+    {
+        if (2 != count($key)) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid document key provided; must contain exactly two elements: a PartitionKey and a RowKey');
+        }
+        foreach ($key as $k) {
+            $this->_validateKey($k);
+        }
+    }
+
+    /**
+     * Validate a document identifier
+     *
+     * If the identifier is an array containing a valid partition and row key, 
+     * returns it. If the identifier is a string:
+     * - if a default partition key is present, it creates an identifier using 
+     *   that and the provided document ID
+     * - if a collection name is provided, it will use that for the partition key
+     * - otherwise, it's invalid
+     * 
+     * @param  array|string $documentId 
+     * @param  null|string $collectionName 
+     * @return array
+     * @throws Zend_Cloud_DocumentService_Exception
+     */
+    protected function _validateDocumentId($documentId, $collectionName = false)
+    {
+        if (is_array($documentId)) {
+            $this->_validateCompositeKey($documentId);
+            return $documentId;
+        }
+        if (!is_string($documentId)) {
+            throw new Zend_Cloud_DocumentService_Exception('Invalid document identifier; must be a string or an array');
+        }
+
+        $this->_validateKey($documentId);
+
+        if (null !== ($partitionKey = $this->getDefaultPartitionKey())) {
+            return array($partitionKey, $documentId);
+        }
+        if (null !== $collectionName) {
+            return array($collectionName, $documentId);
+        }
+        throw new Zend_Cloud_DocumentService_Exception('Cannot determine partition name; invalid document identifier');
+    }
+
+    /**
+     * Validate a document's fields for well-formedness
+     *
+     * Since Azure uses Atom, and fieldnames are included as part of XML 
+     * element tag names, the field names must be valid XML names.
+     *
+     * @param  Zend_Cloud_DocumentService_Document|array $document
+     * @return void
+     * @throws Zend_Cloud_DocumentService_Exception
+     */
+    public function _validateFields($document)
+    {
+        if ($document instanceof Zend_Cloud_DocumentService_Document) {
+            $document = $document->getFields();
+        } elseif (!is_array($document)) {
+            throw new Zend_Cloud_DocumentService_Exception('Cannot inspect fields; invalid type provided');
+        }
+
+        foreach (array_keys($document) as $key) {
+            $this->_validateFieldKey($key);
+        }
+    }
+
+    /**
+     * Validate an individual field name for well-formedness
+     *
+     * Since Azure uses Atom, and fieldnames are included as part of XML 
+     * element tag names, the field names must be valid XML names.
+     *
+     * While we could potentially normalize names, this could also lead to 
+     * conflict with other field names -- which we should avoid. As such,
+     * invalid field names will raise an exception.
+     *
+     * @param  string $key
+     * @return void
+     * @throws Zend_Cloud_DocumentService_Exception
+     */
+    public function _validateFieldKey($key)
+    {
+        if (!preg_match('/^[_A-Za-z][-._A-Za-z0-9]*$/', $key)) {
+            throw new Zend_Cloud_DocumentService_Exception('Field keys must conform to XML names (^[_A-Za-z][-._A-Za-z0-9]*$); key "' . $key . '" does not match');
+        }
+    }
+}

+ 171 - 0
library/Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php

@@ -0,0 +1,171 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/*
+ * @see Zend_Cloud_DocumentService_QueryAdapter
+ */
+require_once 'Zend/Cloud/DocumentService/QueryAdapter.php';
+
+/**
+ * Class implementing Query adapter for working with Azure queries in a 
+ * structured way
+ * 
+ * @todo       Look into preventing a query injection attack.
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query
+    implements Zend_Cloud_DocumentService_QueryAdapter
+{
+    /**
+     * Azure concrete query
+     * 
+     * @var Zend_Service_WindowsAzure_Storage_TableEntityQuery
+     */
+    protected $_azureSelect;
+    
+    /**
+     * Constructor
+     * 
+     * @param  null|Zend_Service_WindowsAzure_Storage_TableEntityQuery $select Table select object
+     * @return void
+     */
+    public function __construct($select = null) 
+    {
+        if (!$select instanceof Zend_Service_WindowsAzure_Storage_TableEntityQuery) {
+            require_once 'Zend/Service/WindowsAzure/Storage/TableEntityQuery.php';
+            $select = new Zend_Service_WindowsAzure_Storage_TableEntityQuery();
+        }
+        $this->_azureSelect = $select;
+    }
+
+    /**
+     * SELECT clause (fields to be selected)
+     * 
+     * Does nothing for Azure.
+     * 
+     * @param  string $select
+     * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query
+     */
+    public function select($select)
+    {
+        return $this;
+    }
+    
+    /**
+     * FROM clause (table name)
+     * 
+     * @param string $from
+     * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query
+     */
+    public function from($from)
+    {
+        $this->_azureSelect->from($from);
+        return $this;
+    }
+    
+    /**
+     * WHERE clause (conditions to be used)
+     * 
+     * @param string $where
+     * @param mixed $value Value or array of values to be inserted instead of ?
+     * @param string $op Operation to use to join where clauses (AND/OR)
+     * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query
+     */
+    public function where($where, $value = null, $op = 'and')
+    {
+        if (!empty($value) && !is_array($value)) {
+            // fix buglet in Azure - numeric values are quoted unless passed as an array
+            $value = array($value);
+        }
+        $this->_azureSelect->where($where, $value, $op);
+        return $this;
+    }
+    
+    /**
+     * WHERE clause for item ID
+     * 
+     * This one should be used when fetching specific rows since some adapters
+     * have special syntax for primary keys
+     * 
+     * @param  array $value Row ID for the document (PartitionKey, RowKey)
+     * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query
+     */
+    public function whereId($value)
+    {
+        if (!is_array($value)) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';
+            throw new Zend_Cloud_DocumentService_Exception('Invalid document key');
+        }
+        $this->_azureSelect->wherePartitionKey($value[0])->whereRowKey($value[1]);
+        return $this;
+    }
+    
+    /**
+     * LIMIT clause (how many rows to return)
+     * 
+     * @param  int $limit
+     * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query
+     */
+    public function limit($limit)
+    {
+        $this->_azureSelect->top($limit);
+        return $this;
+    }
+
+    /**
+     * ORDER BY clause (sorting)
+     * 
+     * @todo   Azure service doesn't seem to support this yet; emulate?
+     * @param  string $sort Column to sort by
+     * @param  string $direction Direction - asc/desc
+     * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query
+     * @throws Zend_Cloud_OperationNotAvailableException
+     */
+    public function order($sort, $direction = 'asc')
+    {
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('No support for sorting for Azure yet');
+    }
+    
+    /**
+     * Get Azure select query
+     * 
+     * @return Zend_Service_WindowsAzure_Storage_TableEntityQuery
+     */
+    public function getAzureSelect()
+    {
+        return  $this->_azureSelect;
+    }
+
+    /**
+     * Assemble query
+     *
+     * Simply return the WindowsAzure table entity query object
+     * 
+     * @return Zend_Service_WindowsAzure_Storage_TableEntityQuery
+     */
+    public function assemble()
+    {
+        return $this->getAzureSelect();
+    }
+}

+ 248 - 0
library/Zend/Cloud/DocumentService/Document.php

@@ -0,0 +1,248 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Class encapsulating documents. Fields are stored in a name/value
+ * array. Data are represented as strings.
+ *
+ * TODO Can fields be large enough to warrant support for streams?
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Document 
+    implements ArrayAccess, IteratorAggregate, Countable
+{
+    /** key in document denoting identifier */
+    const KEY_FIELD = '_id';
+
+    /**
+     * ID of this document.
+     * @var mixed
+     */
+    protected $_id;
+
+    /**
+     * Name/value array of field names to values.
+     * @var array
+     */
+    protected $_fields;
+
+    /**
+     * Construct an instance of Zend_Cloud_DocumentService_Document.
+     *
+     * If no identifier is provided, but a field matching KEY_FIELD is present,
+     * then that field's value will be used as the document identifier.
+     *
+     * @param  array $fields
+     * @param  mixed $id Document identifier
+     * @return void
+     */
+    public function __construct($fields, $id = null)
+    {
+        if (!is_array($fields) && !$fields instanceof ArrayAccess) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';
+            throw new Zend_Cloud_DocumentService_Exception('Fields must be an array or implement ArrayAccess');
+        }
+
+        if (isset($fields[self::KEY_FIELD])) {
+            $id = $fields[self::KEY_FIELD];
+            unset($fields[self::KEY_FIELD]);
+        }
+
+        $this->_fields = $fields;
+        $this->setId($id);
+    }
+
+    /**
+     * Set document identifier
+     * 
+     * @param  mixed $id 
+     * @return Zend_Cloud_DocumentService_Document
+     */
+    public function setId($id)
+    {
+        $this->_id = $id;
+        return $this;
+    }
+
+    /**
+     * Get ID name.
+     *
+     * @return string
+     */
+    public function getId() 
+    {
+        return $this->_id;
+    }
+
+    /**
+     * Get fields as array.
+     *
+     * @return array
+     */
+    public function getFields() 
+    {
+        return $this->_fields;
+    }
+
+    /**
+     * Get field by name.
+     *
+     * @param  string $name
+     * @return mixed
+     */
+    public function getField($name)
+    {
+        if (isset($this->_fields[$name])) {
+            return $this->_fields[$name];
+        }
+        return null;
+    }
+    
+    /**
+     * Set field by name.
+     *
+     * @param  string $name
+     * @param  mixed $value
+     * @return Zend_Cloud_DocumentService_Document
+     */
+    public function setField($name, $value) 
+    {
+        $this->_fields[$name] = $value;
+        return $this;
+    }
+    
+    /**
+     * Overloading: get value
+     * 
+     * @param  string $name 
+     * @return mixed
+     */
+    public function __get($name)
+    {
+        return $this->getField($name);
+    }
+
+    /**
+     * Overloading: set field
+     * 
+     * @param  string $name 
+     * @param  mixed $value 
+     * @return void
+     */
+    public function __set($name, $value)
+    {
+        $this->setField($name, $value);
+    }
+    
+    /**
+     * ArrayAccess: does field exist?
+     * 
+     * @param  string $name 
+     * @return bool
+     */
+    public function offsetExists($name)
+    {
+        return isset($this->_fields[$name]);
+    }
+    
+    /**
+     * ArrayAccess: get field by name
+     * 
+     * @param  string $name 
+     * @return mixed
+     */
+    public function offsetGet($name)
+    {
+        return $this->getField($name);
+    }
+    
+    /**
+     * ArrayAccess: set field to value
+     * 
+     * @param  string $name 
+     * @param  mixed $value 
+     * @return void
+     */
+    public function offsetSet($name, $value)
+    {
+        $this->setField($name, $value);
+    }
+    
+    /**
+     * ArrayAccess: remove field from document
+     * 
+     * @param  string $name 
+     * @return void
+     */
+    public function offsetUnset($name)
+    {
+        if ($this->offsetExists($name)) {
+            unset($this->_fields[$name]);
+        }
+    }
+    
+    /**
+     * Overloading: retrieve and set fields by name
+     * 
+     * @param  string $name 
+     * @param  mixed $args 
+     * @return mixed
+     */
+    public function __call($name, $args)
+    {
+        $prefix = substr($name, 0, 3);
+        if ($prefix == 'get') {
+            // Get value
+            $option = substr($name, 3);
+            return $this->getField($option);
+        } elseif ($prefix == 'set') {
+            // set value
+            $option = substr($name, 3);
+            return $this->setField($option, $args[0]);
+        }
+
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException("Unknown operation $name");
+    }
+
+    /**
+     * Countable: return count of fields in document
+     * 
+     * @return int
+     */
+    public function count()
+    {
+        return count($this->_fields);
+    }
+
+    /**
+     * IteratorAggregate: return iterator for iterating over fields
+     * 
+     * @return Iterator
+     */
+    public function getIterator()
+    {
+        return new ArrayIterator($this->_fields);
+    }
+}

+ 68 - 0
library/Zend/Cloud/DocumentService/DocumentSet.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Class encapsulating a set of documents
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_DocumentSet implements Countable, IteratorAggregate
+{
+    /** @var int */
+    protected $_documentCount;
+
+    /** @var ArrayIterator */
+    protected $_documents;
+
+    /**
+     * Constructor
+     *
+     * @param  array $documents
+     * @return void
+     */
+    public function __construct(array $documents)
+    {
+        $this->_documentCount = count($documents);
+        $this->_documents     = new ArrayIterator($documents);
+    }
+
+    /**
+     * Countable: number of documents in set
+     * 
+     * @return int
+     */
+    public function count()
+    {
+        return $this->_documentCount;
+    }
+
+    /**
+     * IteratorAggregate: retrieve iterator
+     * 
+     * @return Traversable
+     */
+    public function getIterator()
+    {
+        return $this->_documents;
+    }
+}

+ 38 - 0
library/Zend/Cloud/DocumentService/Exception.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+/**
+ * Zend_Cloud_Exception
+ */
+require_once 'Zend/Cloud/Exception.php';
+
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Exception extends Zend_Cloud_Exception
+{}
+

+ 77 - 0
library/Zend/Cloud/DocumentService/Factory.php

@@ -0,0 +1,77 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/AbstractFactory.php';
+
+/**
+ * Class implementing working with Azure queries in a structured way
+ * 
+ * TODO Look into preventing a query injection attack.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Factory extends Zend_Cloud_AbstractFactory
+{
+    const DOCUMENT_ADAPTER_KEY = 'document_adapter';
+
+    /**
+     * @var string Interface which adapter must implement to be considered valid
+     */
+    protected static $_adapterInterface = 'Zend_Cloud_DocumentService_Adapter';
+
+    /**
+     * Constructor
+     * 
+     * @return void
+     */
+    private function __construct()
+    {
+        // private ctor - should not be used
+    }
+    
+    /**
+     * Retrieve an adapter instance
+     * 
+     * @param array $options 
+     * @return void
+     */
+    public static function getAdapter($options = array()) 
+    {
+        $adapter = parent::_getAdapter(self::DOCUMENT_ADAPTER_KEY, $options);
+        if (!$adapter) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';
+            throw new Zend_Cloud_DocumentService_Exception(
+                'Class must be specified using the \''
+                . self::DOCUMENT_ADAPTER_KEY . '\' key'
+            );
+        } elseif (!$adapter instanceof self::$_adapterInterface) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';
+            throw new Zend_Cloud_DocumentService_Exception(
+                'Adapter must implement \'' . self::$_adapterInterface . '\''
+            );
+        }
+        return $adapter;
+    }
+}

+ 191 - 0
library/Zend/Cloud/DocumentService/Query.php

@@ -0,0 +1,191 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/DocumentService/QueryAdapter.php';
+
+/**
+ * Generic query object
+ *
+ * Aggregates operations in an array of clauses, where the first element 
+ * describes the clause type, and the next element describes the criteria.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Query
+    implements Zend_Cloud_DocumentService_QueryAdapter
+{
+    /**
+     * Known query types
+     */
+    const QUERY_SELECT  = 'select';
+    const QUERY_FROM    = 'from';
+    const QUERY_WHERE   = 'where';
+    const QUERY_WHEREID = 'whereid'; // request element by ID
+    const QUERY_LIMIT   = 'limit';
+    const QUERY_ORDER   = 'order';
+
+    /**
+     * Clause list
+     * 
+     * @var array
+     */
+    protected $_clauses = array();
+
+    /**
+     * Generic clause
+     * 
+     * You can use any clause by doing $query->foo('bar')
+     * but concrete adapters should be able to recognise it
+     * 
+     * The call will be iterpreted as clause 'foo' with argument 'bar' 
+     * 
+     * @param  string $name Clause/method name
+     * @param  mixed $args
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function __call($name, $args) 
+    {
+        $this->_clauses[] = array(strtolower($name), $args);
+        return $this;
+    }
+
+    /**
+     * SELECT clause (fields to be selected)
+     * 
+     * @param  null|string|array $select 
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function select($select)
+    {
+        if (empty($select)) {
+            return $this;
+        }
+        if (!is_string($select) && !is_array($select)) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';           
+            throw new Zend_Cloud_DocumentService_Exception("SELECT argument must be a string or an array of strings");
+        }
+        $this->_clauses[] = array(self::QUERY_SELECT, $select);
+        return $this;
+    }
+    
+    /**
+     * FROM clause
+     * 
+     * @param string $name Field names  
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function from($name)
+    {
+        if(!is_string($name)) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';           
+            throw new Zend_Cloud_DocumentService_Exception("FROM argument must be a string");
+        }
+        $this->_clauses[] = array(self::QUERY_FROM, $name);
+        return $this;
+    }
+    
+    /**
+     * WHERE query
+     * 
+     * @param string $cond Condition
+     * @param array $args Arguments to substitute instead of ?'s in condition
+     * @param string $op relation to other clauses - and/or
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function where($cond, $value = null, $op = 'and')
+    {
+        if (!is_string($cond)) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';           
+            throw new Zend_Cloud_DocumentService_Exception("WHERE argument must be a string");
+        }
+        $this->_clauses[] = array(self::QUERY_WHERE, array($cond, $value, $op));
+        return $this;
+    }
+
+    /**
+     * Select record or fields by ID
+     * 
+     * @param  string|int $value Identifier to select by
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function whereId($value)
+    {
+        if (!is_scalar($value)) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';           
+            throw new Zend_Cloud_DocumentService_Exception("WHEREID argument must be a scalar");
+        }
+        $this->_clauses[] = array(self::QUERY_WHEREID, $value);
+        return $this;
+    }
+
+    /**
+     * LIMIT clause (how many items to return)
+     * 
+     * @param  int $limit 
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function limit($limit)
+    {
+        if ($limit != (int) $limit) {
+            require_once 'Zend/Cloud/DocumentService/Exception.php';           
+            throw new Zend_Cloud_DocumentService_Exception("LIMIT argument must be an integer");
+        }
+        $this->_clauses[] = array(self::QUERY_LIMIT, $limit);
+        return $this;
+    }
+
+    /**
+     * ORDER clause; field or fields to sort by, and direction to sort
+     * 
+     * @param  string|int|array $sort 
+     * @param  string $direction 
+     * @return Zend_Cloud_DocumentService_Query
+     */
+    public function order($sort, $direction = 'asc')
+    {
+        $this->_clauses[] = array(self::QUERY_ORDER, array($sort, $direction));
+        return $this;
+    }
+
+    /**
+     * "Assemble" the query
+     *
+     * Simply returns the clauses present.
+     * 
+     * @return array
+     */
+    public function assemble()
+    {
+        return $this->getClauses();
+    }
+    
+    /**
+     * Return query clauses as an array
+     * 
+     * @return array Clauses in the query
+     */
+    public function getClauses()
+    {
+         return $this->_clauses;   
+    }
+}

+ 102 - 0
library/Zend/Cloud/DocumentService/QueryAdapter.php

@@ -0,0 +1,102 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * This interface describes the API that concrete query adapter should implement
+ * 
+ * Common interface for document storage services in the cloud. This interface
+ * supports most document services and provides some flexibility for
+ * vendor-specific features and requirements via an optional $options array in
+ * each method signature. Classes implementing this interface should implement
+ * URI construction for collections and documents from the parameters given in each
+ * method and the account data passed in to the constructor. Classes
+ * implementing this interface are also responsible for security; access control
+ * isn't currently supported in this interface, although we are considering
+ * access control support in future versions of the interface. Query
+ * optimization mechanisms are also not supported in this version.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+interface Zend_Cloud_DocumentService_QueryAdapter
+{
+    /**
+     * SELECT clause (fields to be selected)
+     * 
+     * @param string $select
+     * @return Zend_Cloud_DocumentService_QueryAdapter
+     */
+    public function select($select);
+
+    /**
+     * FROM clause (table name)
+     * 
+     * @param string $from
+     * @return Zend_Cloud_DocumentService_QueryAdapter
+     */
+    public function from($from);
+
+    /**
+     * WHERE clause (conditions to be used)
+     * 
+     * @param string $where
+     * @param mixed $value Value or array of values to be inserted instead of ?
+     * @param string $op Operation to use to join where clauses (AND/OR)
+     * @return Zend_Cloud_DocumentService_QueryAdapter
+     */
+    public function where($where, $value = null, $op = 'and');
+
+    /**
+     * WHERE clause for item ID
+     * 
+     * This one should be used when fetching specific rows since some adapters
+     * have special syntax for primary keys
+     * 
+     * @param mixed $value Row ID for the document
+     * @return Zend_Cloud_DocumentService_QueryAdapter
+     */
+    public function whereId($value);
+
+    /**
+     * LIMIT clause (how many rows ot return)
+     * 
+     * @param int $limit
+     * @return Zend_Cloud_DocumentService_QueryAdapter
+     */
+    public function limit($limit);
+
+    /**
+     * ORDER BY clause (sorting)
+     * 
+     * @param string $sort Column to sort by
+     * @param string $direction Direction - asc/desc
+     * @return Zend_Cloud_DocumentService_QueryAdapter
+     */
+    public function order($sort, $direction = 'asc');
+
+    /**
+     * Assemble the query into a format the adapter can utilize
+     * 
+     * @return mixed
+     */
+    public function assemble();
+}

+ 53 - 0
library/Zend/Cloud/Exception.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+/**
+ * Zend_Exception
+ */
+require_once 'Zend/Exception.php';
+
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_Exception extends Zend_Exception
+{ 
+    /**
+     * Exception for the underlying adapter
+     * 
+     * @var Exception
+     */
+    protected $_clientException;
+    
+    public function __construct($message, $code = 0, $clientException = null) 
+    {
+        $this->_clientException = $clientException;
+        parent::__construct($message, $code, $clientException);
+    }
+
+    public function getClientException() {
+        return $this->_getPrevious();
+    }
+}
+

+ 34 - 0
library/Zend/Cloud/OperationNotAvailableException.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Zend_Exception
+ */
+require_once 'Zend/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_OperationNotAvailableException extends Zend_Exception
+{}
+

+ 146 - 0
library/Zend/Cloud/QueueService/Adapter.php

@@ -0,0 +1,146 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Common interface for queue services in the cloud. This interface supports
+ * most queue services and provides some flexibility for vendor-specific
+ * features and requirements via an optional $options array in each method
+ * signature. Classes implementing this interface should implement URI
+ * construction for queues from the parameters given in each method and the
+ * account data passed in to the constructor. Classes implementing this
+ * interface are also responsible for security; access control isn't currently
+ * supported in this interface, although we are considering access control
+ * support in future versions of the interface.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+interface Zend_Cloud_QueueService_Adapter
+{
+    /** Ctor HTTP adapter option */
+    const HTTP_ADAPTER = 'http_adapter';
+
+    /** Message visibility timeout option */
+    const VISIBILITY_TIMEOUT = 'visibility_timeout';
+
+    /** Default visibility timeout */
+    const DEFAULT_TIMEOUT = 30;
+
+    /**
+     * Create a queue. Returns the ID of the created queue (typically the URL).
+     * It may take some time to create the queue. Check your vendor's
+     * documentation for details.
+     * 
+     * Name constraints: Maximum 80 characters
+     *                      Only alphanumeric characters, hyphens (-), and underscores (_)
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return string Queue ID (typically URL)
+     */
+    public function createQueue($name, $options = null);
+
+    /**
+     * Delete a queue. All messages in the queue will also be deleted.
+     *
+     * @param  string $queueId
+     * @param  array  $options
+     * @return boolean true if successful, false otherwise
+     */
+    public function deleteQueue($queueId, $options = null);
+    
+    /**
+     * List all queues.
+     *
+     * @param  array $options
+     * @return array Queue IDs
+     */
+    public function listQueues($options = null);
+    
+    /**
+     * Get a key/value array of metadata for the given queue.
+     *
+     * @param  string $queueId
+     * @param  array  $options
+     * @return array
+     */
+    public function fetchQueueMetadata($queueId, $options = null);
+    
+    /**
+     * Store a key/value array of metadata for the specified queue.
+     * WARNING: This operation overwrites any metadata that is located at 
+     * $destinationPath. Some adapters may not support this method.
+     * 
+     * @param  string $queueId
+     * @param  array  $metadata
+     * @param  array  $options
+     * @return void
+     */
+    public function storeQueueMetadata($queueId, $metadata,  $options = null);
+    
+    /**
+     * Send a message to the specified queue.
+     * 
+     * @param  string $queueId
+     * @param  string $message
+     * @param  array  $options
+     * @return string Message ID
+     */
+    public function sendMessage($queueId, $message,  $options = null);
+    
+    /**
+     * Recieve at most $max messages from the specified queue and return the
+     * message IDs for messages recieved.
+     * 
+     * @param  string $queueId
+     * @param  int    $max
+     * @param  array  $options
+     * @return array[Zend_Cloud_QueueService_Message]  Array of messages
+     */
+    public function receiveMessages($queueId, $max = 1, $options = null);
+        
+    /**
+     * Peek at the messages from the specified queue without removing them.
+     *
+     * @param  string $queueId
+     * @param  int $num How many messages
+     * @param  array  $options
+     * @return array[Zend_Cloud_QueueService_Message]
+     */
+    public function peekMessages($queueId, $num = 1, $options = null);
+    
+    /**
+     * Delete the specified message from the specified queue.
+     * 
+     * @param  string $queueId
+     * @param  Zend_Cloud_QueueService_Message $message Message to delete 
+     * @param  array  $options
+     * @return void
+     * 
+     */
+    public function deleteMessage($queueId, $message,  $options = null);
+    
+    /**
+     * Get the concrete adapter.
+     */
+    public function getClient();
+}

+ 92 - 0
library/Zend/Cloud/QueueService/Adapter/AbstractAdapter.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/QueueService/Adapter.php';
+require_once 'Zend/Cloud/QueueService/Message.php';
+require_once 'Zend/Cloud/QueueService/MessageSet.php';
+
+/**
+ * Abstract queue adapter
+ *
+ * Provides functionality around setting message and message set classes.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Cloud_QueueService_Adapter_AbstractAdapter
+    implements Zend_Cloud_QueueService_Adapter
+{
+    /**@+ option keys */
+    const MESSAGE_CLASS    = 'message_class';
+    const MESSAGESET_CLASS = 'messageset_class';
+    /**@-*/
+
+    /** @var string Class to use for queue messages */
+    protected $_messageClass    = 'Zend_Cloud_QueueService_Message';
+
+    /** @var string Class to use for collections of queue messages */
+    protected $_messageSetClass = 'Zend_Cloud_QueueService_MessageSet';
+
+    /**
+     * Set class to use for message objects
+     * 
+     * @param  string $class 
+     * @return Zend_Cloud_QueueService_Adapter_AbstractAdapter
+     */
+    public function setMessageClass($class)
+    {
+        $this->_messageClass = (string) $class;
+        return $this;
+    }
+
+    /**
+     * Get class to use for message objects
+     * 
+     * @return string
+     */
+    public function getMessageClass()
+    {
+        return $this->_messageClass;
+    }
+
+    /**
+     * Set class to use for message collection objects
+     * 
+     * @param  string $class 
+     * @return Zend_Cloud_QueueService_Adapter_AbstractAdapter
+     */
+    public function setMessageSetClass($class)
+    {
+        $this->_messageSetClass = (string) $class;
+        return $this;
+    }
+
+    /**
+     * Get class to use for message collection objects
+     * 
+     * @return string
+     */
+    public function getMessageSetClass()
+    {
+        return $this->_messageSetClass;
+    }
+}

+ 278 - 0
library/Zend/Cloud/QueueService/Adapter/Sqs.php

@@ -0,0 +1,278 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Service/Amazon/Sqs.php';
+require_once 'Zend/Cloud/QueueService/Adapter/AbstractAdapter.php';
+require_once 'Zend/Cloud/QueueService/Exception.php';
+require_once 'Zend/Cloud/QueueService/Message.php';
+
+/**
+ * SQS adapter for simple queue service.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Adapter_Sqs 
+    extends Zend_Cloud_QueueService_Adapter_AbstractAdapter
+{
+    /*
+     * Options array keys for the SQS adapter.
+     */
+    const AWS_ACCESS_KEY = 'aws_accesskey';
+    const AWS_SECRET_KEY = 'aws_secretkey';
+
+    /**
+     * Defaults
+     */
+    const CREATE_TIMEOUT = 30;
+
+    /**
+     * SQS service instance.
+     * @var Zend_Service_Amazon_Sqs
+     */
+    protected $_sqs;
+
+    /**
+     * Constructor
+     * 
+     * @param  array|Zend_Config $options 
+     * @return void
+     */
+    public function __construct($options = array()) 
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_QueueService_Exception('Invalid options provided');
+        }
+
+        if (isset($options[self::MESSAGE_CLASS])) {
+            $this->setMessageClass($options[self::MESSAGE_CLASS]);
+        }
+
+        if (isset($options[self::MESSAGESET_CLASS])) {
+            $this->setMessageSetClass($options[self::MESSAGESET_CLASS]);
+        }
+
+        try {
+            $this->_sqs = new Zend_Service_Amazon_Sqs(
+                $options[self::AWS_ACCESS_KEY], $options[self::AWS_SECRET_KEY]
+            );
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e);
+        }
+
+        if(isset($options[self::HTTP_ADAPTER])) {
+            $this->_sqs->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]);
+        } 
+    }
+
+     /**
+     * Create a queue. Returns the ID of the created queue (typically the URL).
+     * It may take some time to create the queue. Check your vendor's
+     * documentation for details.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return string Queue ID (typically URL)
+     */
+    public function createQueue($name, $options = null) 
+    {
+        try {
+            return $this->_sqs->create($name, $options[self::CREATE_TIMEOUT]);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on queue creation: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Delete a queue. All messages in the queue will also be deleted.
+     *
+     * @param  string $queueId
+     * @param  array  $options
+     * @return boolean true if successful, false otherwise
+     */
+    public function deleteQueue($queueId, $options = null) 
+{
+        try {
+            return $this->_sqs->delete($queueId);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw Zend_Cloud_QueueService_Exception('Error on queue deletion: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * List all queues.
+     *
+     * @param  array $options
+     * @return array Queue IDs
+     */
+    public function listQueues($options = null) 
+    {
+        try {
+            return $this->_sqs->getQueues();
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on listing queues: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Get a key/value array of metadata for the given queue.
+     *
+     * @param  string $queueId
+     * @param  array  $options
+     * @return array
+     */
+    public function fetchQueueMetadata($queueId, $options = null) 
+    {
+        try {
+            // TODO: ZF-9050 Fix the SQS client library in trunk to return all attribute values
+            $attributes = $this->_sqs->getAttribute($queueId, 'All');
+            if(is_array($attributes)) {
+                return $attributes;
+            } else {
+                return array('All' => $this->_sqs->getAttribute($queueId, 'All'));
+            }
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on fetching queue metadata: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Store a key/value array of metadata for the specified queue.
+     * WARNING: This operation overwrites any metadata that is located at
+     * $destinationPath. Some adapters may not support this method.
+     *
+     * @param  array  $metadata
+     * @param  string $queueId
+     * @param  array  $options
+     * @return void
+     */
+    public function storeQueueMetadata($queueId, $metadata, $options = null) 
+    {
+        // TODO Add support for SetQueueAttributes to client library
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('Amazon SQS doesn\'t currently support storing metadata');
+    }
+
+    /**
+     * Send a message to the specified queue.
+     *
+     * @param  string $message
+     * @param  string $queueId
+     * @param  array  $options
+     * @return string Message ID
+     */
+    public function sendMessage($queueId, $message, $options = null) 
+    {
+        try {
+            return $this->_sqs->send($queueId, $message);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on sending message: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Recieve at most $max messages from the specified queue and return the
+     * message IDs for messages recieved.
+     *
+     * @param  string $queueId
+     * @param  int    $max
+     * @param  array  $options
+     * @return array
+     */
+    public function receiveMessages($queueId, $max = 1, $options = null) 
+    {
+        try {
+            return $this->_makeMessages($this->_sqs->receive($queueId, $max, $options[self::VISIBILITY_TIMEOUT]));
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on recieving messages: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Create Zend_Cloud_QueueService_Message array for
+     * Sqs messages.
+     *  
+     * @param array $messages
+     * @return Zend_Cloud_QueueService_Message[]
+     */
+    protected function _makeMessages($messages)
+    {
+        $messageClass = $this->getMessageClass();
+        $setClass     = $this->getMessageSetClass();
+        $result = array();
+        foreach($messages as $message) {
+            $result[] = new $messageClass($message['body'], $message);
+        }
+        return new $setClass($result);
+    }
+
+    /**
+     * Delete the specified message from the specified queue.
+     *
+     * @param  string $queueId
+     * @param  Zend_Cloud_QueueService_Message $message
+     * @param  array  $options
+     * @return void
+     */
+    public function deleteMessage($queueId, $message, $options = null) 
+    {
+        try {
+            if($message instanceof Zend_Cloud_QueueService_Message) {
+                $message = $message->getMessage();
+            }
+            $messageId = $message['handle'];
+            return $this->_sqs->deleteMessage($queueId, $messageId);
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on deleting a message: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Peek at the messages from the specified queue without removing them.
+     *
+     * @param  string $queueId
+     * @param  int $num How many messages
+     * @param  array  $options
+     * @return Zend_Cloud_QueueService_Message[]
+     */
+    public function peekMessages($queueId, $num = 1, $options = null)
+    {
+        try {
+            return $this->_makeMessages($this->_sqs->receive($queueId, $num, 0));
+        } catch(Zend_Service_Amazon_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on peeking messages: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Get SQS implementation
+     * @return Zend_Service_Amazon_Sqs 
+     */
+    public function getClient()
+    {
+        return $this->_sqs;
+    }
+}

+ 343 - 0
library/Zend/Cloud/QueueService/Adapter/WindowsAzure.php

@@ -0,0 +1,343 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/QueueService/Adapter/AbstractAdapter.php';
+require_once 'Zend/Cloud/QueueService/Exception.php';
+require_once 'Zend/Service/WindowsAzure/Storage/Queue.php';
+
+/**
+ * WindowsAzure adapter for simple queue service.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Adapter_WindowsAzure 
+    extends Zend_Cloud_QueueService_Adapter_AbstractAdapter
+{
+    /**
+     * Option array keys for the Windows Azure adapter.
+     */
+    const ACCOUNT_NAME      = 'storage_accountname';
+    const ACCOUNT_KEY       = 'storage_accountkey';
+    const HOST              = "storage_host";
+    const PROXY_HOST        = "storage_proxy_host";
+    const PROXY_PORT        = "storage_proxy_port";
+    const PROXY_CREDENTIALS = "storage_proxy_credentials";
+
+    /** list options */
+    const LIST_PREFIX      = 'prefix';
+    const LIST_MAX_RESULTS = 'max_results';
+
+    /** message options */
+    const MESSAGE_TTL = 'ttl';
+    
+    const DEFAULT_HOST = Zend_Service_WindowsAzure_Storage::URL_CLOUD_QUEUE;
+
+    /**
+     * Storage client
+     * 
+     * @var Zend_Service_WindowsAzure_Storage_Queue
+     */
+    protected $_storageClient = null;
+    
+    /**
+     * Constructor
+     * 
+     * @param  array|Zend_Config $options 
+     * @return void
+     */
+    public function __construct($options = array())
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_QueueService_Exception('Invalid options provided');
+        }
+
+        if (isset($options[self::MESSAGE_CLASS])) {
+            $this->setMessageClass($options[self::MESSAGE_CLASS]);
+        }
+
+        if (isset($options[self::MESSAGESET_CLASS])) {
+            $this->setMessageSetClass($options[self::MESSAGESET_CLASS]);
+        }
+
+        // Build Zend_Service_WindowsAzure_Storage_Blob instance
+        if (!isset($options[self::HOST])) {
+            $host = self::DEFAULT_HOST;
+        } else {
+            $host = $options[self::HOST];
+        }
+        if (! isset($options[self::ACCOUNT_NAME])) {
+            throw new Zend_Cloud_Storage_Exception('No Windows Azure account name provided.');
+        }
+        if (! isset($options[self::ACCOUNT_KEY])) {
+            throw new Zend_Cloud_Storage_Exception('No Windows Azure account key provided.');
+        }
+        try {
+            // TODO: support $usePathStyleUri and $retryPolicy
+            $this->_storageClient = new Zend_Service_WindowsAzure_Storage_Queue(
+                $host, $options[self::ACCOUNT_NAME], $options[self::ACCOUNT_KEY]);
+            // Parse other options
+            if (! empty($options[self::PROXY_HOST])) {
+                $proxyHost = $options[self::PROXY_HOST];
+                $proxyPort = isset($options[self::PROXY_PORT]) ? $options[self::PROXY_PORT] : 8080;
+                $proxyCredentials = isset($options[self::PROXY_CREDENTIALS]) ? $options[self::PROXY_CREDENTIALS] : '';
+                $this->_storageClient->setProxy(true, $proxyHost, $proxyPort, $proxyCredentials);
+            }
+            if (isset($options[self::HTTP_ADAPTER])) {
+                $this->_storageClient->setHttpClientChannel($httpAdapter);
+            }
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e);
+        }
+            
+    }
+    
+    /**
+     * Create a queue. Returns the ID of the created queue (typically the URL).
+     * It may take some time to create the queue. Check your vendor's
+     * documentation for details.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return string Queue ID (typically URL)
+     */
+    public function createQueue($name, $options = null)
+    {
+        try {
+            $queue = $this->_storageClient->createQueue($name, $options);
+            return $queue->Name;
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on queue creation: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Delete a queue. All messages in the queue will also be deleted.
+     *
+     * @param  string $queueId
+     * @param  array  $options
+     * @return boolean true if successful, false otherwise
+     */
+    public function deleteQueue($queueId, $options = null)
+    {
+        try {
+            if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) {
+                $queueId = $queueId->Name;
+            }
+            return $this->_storageClient->deleteQueue($queueId);
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on queue deletion: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * List all queues.
+     *
+     * @param  array $options
+     * @return array Queue IDs
+     */
+    public function listQueues($options = null)
+    {
+        $prefix = $maxResults = null;
+        if (is_array($options)) {
+            isset($options[self::LIST_PREFIX]) ? $prefix = $options[self::LIST_PREFIX] : null;
+            isset($options[self::LIST_MAX_RESULTS]) ? $maxResults = $options[self::LIST_MAX_RESULTS] : null;
+        }
+        try {
+            $queues =  $this->_storageClient->listQueues($prefix, $maxResults);
+            $result = array();
+            foreach ($queues as $queue) {
+                $result[] = $queue->Name;
+            }
+            return $result;
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on listing queues: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Get a key/value array of metadata for the given queue.
+     *
+     * @param  string $queueId
+     * @param  array  $options
+     * @return array
+     */
+    public function fetchQueueMetadata($queueId, $options = null)
+    {
+        try {
+            if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) {
+                $queueId = $queueId->Name;
+            }
+            return $this->_storageClient->getQueueMetadata($queueId);
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on fetching queue metadata: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Store a key/value array of metadata for the specified queue.
+     * WARNING: This operation overwrites any metadata that is located at 
+     * $destinationPath. Some adapters may not support this method.
+     * 
+     * @param  string $queueId
+     * @param  array  $metadata
+     * @param  array  $options
+     * @return void
+     */
+    public function storeQueueMetadata($queueId, $metadata, $options = null)
+    {
+        try {
+            if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) {
+                $queueId = $queueId->Name;
+            }
+            return $this->_storageClient->setQueueMetadata($queueId, $metadata);
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on setting queue metadata: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Send a message to the specified queue.
+     * 
+     * @param  string $queueId
+     * @param  string $message
+     * @param  array  $options
+     * @return string Message ID
+     */
+    public function sendMessage($queueId, $message, $options = null)
+    {
+        try {
+            if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) {
+                $queueId = $queueId->Name;
+            }
+            return $this->_storageClient->putMessage(
+                $queueId, $message, $options[self::MESSAGE_TTL]
+            );
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on sending message: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Recieve at most $max messages from the specified queue and return the
+     * message IDs for messages recieved.
+     * 
+     * @param  string $queueId
+     * @param  int    $max
+     * @param  array  $options
+     * @return Zend_Cloud_QueueService_Message[]
+     */
+    public function receiveMessages($queueId, $max = 1, $options = null)
+    {
+        try {
+            if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) {
+                $queueId = $queueId->Name;
+            }
+            if (isset($options[self::VISIBILITY_TIMEOUT])) {
+                $visibility = $options[self::VISIBILITY_TIMEOUT];
+            } else {
+                $visibility = self::DEFAULT_TIMEOUT;
+            }
+            return $this->_makeMessages($this->_storageClient->getMessages($queueId, $max, $visibility, false));
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on recieving messages: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Create Zend_Cloud_QueueService_Message array for
+     * Azure messages.
+     *  
+     * @param array $messages
+     * @return Zend_Cloud_QueueService_Message[]
+     */
+    protected function _makeMessages($messages)
+    {
+        $messageClass = $this->getMessageClass();
+        $setClass     = $this->getMessageSetClass();
+        $result = array();
+        foreach ($messages as $message) {
+            $result[] = new $messageClass($message->MessageText, $message);
+        }
+        return new $setClass($result);
+    }
+
+    /**
+     * Delete the specified message from the specified queue.
+     * 
+     * @param  string $queueId
+     * @param  Zend_Cloud_QueueService_Message $message Message ID or message 
+     * @param  array  $options
+     * @return void
+     */
+    public function deleteMessage($queueId, $message, $options = null)
+    {
+        try {
+            if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) {
+                $queueId = $queueId->Name;
+            }
+            if ($message instanceof Zend_Cloud_QueueService_Message) {
+                $message = $message->getMessage();
+            }
+            if ($message instanceof Zend_Service_WindowsAzure_Storage_QueueMessage) {
+                return $this->_storageClient->deleteMessage($queueId, $message);
+            } else {
+                throw new Zend_Cloud_QueueService_Exception('Cannot delete the message: message object required');
+            }
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on deleting a message: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Peek at the messages from the specified queue without removing them.
+     *
+     * @param  string $queueId
+     * @param  int $num How many messages
+     * @param  array  $options
+     * @return Zend_Cloud_QueueService_Message[]
+     */
+    public function peekMessages($queueId, $num = 1, $options = null)
+    {
+        try {
+            if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) {
+                $queueId = $queueId->Name;
+            }
+            return $this->_makeMessages($this->_storageClient->peekMessages($queueId, $num));
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on peeking messages: '.$e->getMessage(), $e->getCode(), $e);
+        }
+   }
+    
+    /**
+     * Get Azure implementation
+     * @return Zend_Service_Azure_Storage_Queue 
+     */
+    public function getClient()
+    {
+        return $this->_storageClient;
+    }
+}

+ 301 - 0
library/Zend/Cloud/QueueService/Adapter/ZendQueue.php

@@ -0,0 +1,301 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/QueueService/Adapter/AbstractAdapter.php';
+require_once 'Zend/Cloud/QueueService/Exception.php';
+require_once 'Zend/Queue.php';
+
+/**
+ * WindowsAzure adapter for simple queue service.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Adapter_ZendQueue 
+    extends Zend_Cloud_QueueService_Adapter_AbstractAdapter
+{
+    /**
+     * Options array keys for the Zend_Queue adapter.
+     */
+    const ADAPTER = 'adapter';
+    
+    /**
+     * Storage client
+     * 
+     * @var Zend_Queue
+     */
+    protected $_queue = null;
+    
+    /**
+     * @var array All queues
+     */
+    protected $_queues = array();
+    
+    /**
+     * Constructor
+     * 
+     * @param  array|Zend_Config $options 
+     * @return void
+     */
+    public function __construct ($options = array())
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_QueueService_Exception('Invalid options provided');
+        }
+
+        if (isset($options[self::MESSAGE_CLASS])) {
+            $this->setMessageClass($options[self::MESSAGE_CLASS]);
+        }
+
+        if (isset($options[self::MESSAGESET_CLASS])) {
+            $this->setMessageSetClass($options[self::MESSAGESET_CLASS]);
+        }
+
+        // Build Zend_Service_WindowsAzure_Storage_Blob instance
+        if (!isset($options[self::ADAPTER])) {
+            throw new Zend_Cloud_QueueService_Exception('No Zend_Queue adapter provided');
+        } else {
+            $adapter = $options[self::ADAPTER];
+            unset($options[self::ADAPTER]);
+        }
+        try {
+            $this->_queue = new Zend_Queue($adapter, $options);
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Create a queue. Returns the ID of the created queue (typically the URL).
+     * It may take some time to create the queue. Check your vendor's
+     * documentation for details.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return string Queue ID (typically URL)
+     */
+    public function createQueue($name, $options = null)
+    {
+        try {
+            $this->_queues[$name] = $this->_queue->createQueue($name, isset($options[Zend_Queue::TIMEOUT])?$options[Zend_Queue::TIMEOUT]:null);
+            return $name;
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on queue creation: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Delete a queue. All messages in the queue will also be deleted.
+     *
+     * @param  string $queueId
+     * @param  array  $options
+     * @return boolean true if successful, false otherwise
+     */
+    public function deleteQueue($queueId, $options = null)
+    {
+        if (!isset($this->_queues[$queueId])) {
+            return false;
+        }
+        try {
+            if ($this->_queues[$queueId]->deleteQueue()) {
+                unset($this->_queues[$queueId]);
+                return true;
+            }
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on queue deletion: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * List all queues.
+     *
+     * @param  array $options
+     * @return array Queue IDs
+     */
+    public function listQueues($options = null)
+    {
+        try {
+            return $this->_queue->getQueues();
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on listing queues: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Get a key/value array of metadata for the given queue.
+     *
+     * @param  string $queueId
+     * @param  array  $options
+     * @return array
+     */
+    public function fetchQueueMetadata($queueId, $options = null)
+    {
+        if (!isset($this->_queues[$queueId])) {
+            return false;
+        }
+        try {
+            return $this->_queues[$queueId]->getOptions();
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on fetching queue metadata: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Store a key/value array of metadata for the specified queue.
+     * WARNING: This operation overwrites any metadata that is located at 
+     * $destinationPath. Some adapters may not support this method.
+     * 
+     * @param  string $queueId
+     * @param  array  $metadata
+     * @param  array  $options
+     * @return void
+     */
+    public function storeQueueMetadata($queueId, $metadata, $options = null)
+    {
+        if (!isset($this->_queues[$queueId])) {
+            throw new Zend_Cloud_QueueService_Exception("No such queue: $queueId");
+        }
+        try {
+            return $this->_queues[$queueId]->setOptions($metadata);
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on setting queue metadata: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Send a message to the specified queue.
+     * 
+     * @param  string $queueId
+     * @param  string $message
+     * @param  array  $options
+     * @return string Message ID
+     */
+    public function sendMessage($queueId, $message, $options = null)
+    {
+        if (!isset($this->_queues[$queueId])) {
+            throw new Zend_Cloud_QueueService_Exception("No such queue: $queueId");
+        }
+        try {
+            return $this->_queues[$queueId]->send($message);
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on sending message: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Recieve at most $max messages from the specified queue and return the
+     * message IDs for messages recieved.
+     * 
+     * @param  string $queueId
+     * @param  int    $max
+     * @param  array  $options
+     * @return array
+     */
+    public function receiveMessages($queueId, $max = 1, $options = null)
+    {
+        if (!isset($this->_queues[$queueId])) {
+            throw new Zend_Cloud_QueueService_Exception("No such queue: $queueId");
+        }
+        try {
+            $res = $this->_queues[$queueId]->receive($max, isset($options[Zend_Queue::TIMEOUT])?$options[Zend_Queue::TIMEOUT]:null);
+            if ($res instanceof Iterator) {
+                return $this->_makeMessages($res);
+            } else {
+                return $this->_makeMessages(array($res));
+            }
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on recieving messages: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Create Zend_Cloud_QueueService_Message array for
+     * Azure messages.
+     *  
+     * @param array $messages
+     * @return Zend_Cloud_QueueService_Message[]
+     */
+    protected function _makeMessages($messages)
+    {
+        $messageClass = $this->getMessageClass();
+        $setClass     = $this->getMessageSetClass();
+        $result = array();
+        foreach ($messages as $message) {
+            $result[] = new $messageClass($message->body, $message);
+        }
+        return new $setClass($result);
+    }
+    
+    /**
+     * Delete the specified message from the specified queue.
+     * 
+     * @param  string $queueId
+     * @param  Zend_Cloud_QueueService_Message $message Message ID or message 
+     * @param  array  $options
+     * @return void
+     */
+    public function deleteMessage($queueId, $message, $options = null)
+    {
+        if (!isset($this->_queues[$queueId])) {
+            throw new Zend_Cloud_QueueService_Exception("No such queue: $queueId");
+        }
+        try {
+            if ($message instanceof Zend_Cloud_QueueService_Message) {
+                $message = $message->getMessage();
+            }
+            if (!($message instanceof Zend_Queue_Message)) {
+                throw new Zend_Cloud_QueueService_Exception('Cannot delete the message: Zend_Queue_Message object required');
+            }
+            
+            return $this->_queues[$queueId]->deleteMessage($message);
+        } catch (Zend_Queue_Exception $e) {
+            throw new Zend_Cloud_QueueService_Exception('Error on deleting a message: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Peek at the messages from the specified queue without removing them.
+     *
+     * @param  string $queueId
+     * @param  int $num How many messages
+     * @param  array  $options
+     * @return Zend_Cloud_QueueService_Message[]
+     */
+    public function peekMessages($queueId, $num = 1, $options = null)
+    {
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('ZendQueue doesn\'t currently support message peeking');
+    }
+    
+    /**
+     * Get Azure implementation
+     * @return Zend_Queue 
+     */
+    public function getClient()
+    {
+        return $this->_queue;
+    }
+}

+ 37 - 0
library/Zend/Cloud/QueueService/Exception.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+/**
+ * Zend_Cloud_Exception
+ */
+require_once 'Zend/Cloud/Exception.php';
+
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Exception extends Zend_Cloud_Exception
+{}

+ 71 - 0
library/Zend/Cloud/QueueService/Factory.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/AbstractFactory.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Factory extends Zend_Cloud_AbstractFactory
+{
+    const QUEUE_ADAPTER_KEY = 'queue_adapter';
+
+    /**
+     * @var string Interface which adapter must implement to be considered valid
+     */
+    protected static $_adapterInterface = 'Zend_Cloud_QueueService_Adapter';
+
+    /**
+     * Constructor
+     * 
+     * @return void
+     */
+    private function __construct()
+    {
+        // private ctor - should not be used
+    }
+    
+    /**
+     * Retrieve QueueService adapter
+     * 
+     * @param  array $options 
+     * @return void
+     */
+    public static function getAdapter($options = array()) 
+    {
+        $adapter = parent::_getAdapter(self::QUEUE_ADAPTER_KEY, $options);
+        if (!$adapter) {
+            require_once 'Zend/Cloud/QueueService/Exception.php';
+            throw new Zend_Cloud_QueueService_Exception('Class must be specified using the \'' .
+            self::QUEUE_ADAPTER_KEY . '\' key');
+        } elseif (!$adapter instanceof self::$_adapterInterface) {
+            require_once 'Zend/Cloud/QueueService/Exception.php';
+            throw new Zend_Cloud_QueueService_Exception(
+                'Adapter must implement \'' . self::$_adapterInterface . '\''
+            );
+        }
+        return $adapter;
+    }
+}

+ 60 - 0
library/Zend/Cloud/QueueService/Message.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Generic message class
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Message
+{
+    protected $_body;
+    protected $_clientMessage;
+    
+    /**
+     * @param string $body Message text
+     * @param $message Original message
+     */
+    function __construct($body, $message)
+    {
+        $this->_body = $body;  
+        $this->_clientMessage = $message;      
+    }
+
+    /**
+     * Get the message body
+     * @return string
+     */
+    public function getBody()
+    {
+        return $this->_body;
+    }
+    
+    /**
+     * Get the original adapter-specific message
+     */
+    public function getMessage()
+    {
+        return $this->_clientMessage;
+    }
+}

+ 68 - 0
library/Zend/Cloud/QueueService/MessageSet.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Collection of message objects
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage QueueService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_MessageSet implements Countable, IteratorAggregate
+{
+    /** @var int */
+    protected $_messageCount;
+
+    /** @var ArrayAccess Messages */
+    protected $_messages;
+
+    /**
+     * Constructor
+     * 
+     * @param  array $messages 
+     * @return void
+     */
+    public function __construct(array $messages)
+    {
+        $this->_messageCount = count($messages);
+        $this->_messages     = new ArrayIterator($messages);
+    }
+
+    /**
+     * Countable: number of messages in collection
+     * 
+     * @return int
+     */
+    public function count()
+    {
+        return $this->_messageCount;
+    }
+
+    /**
+     * IteratorAggregate: return iterable object
+     * 
+     * @return Traversable
+     */
+    public function getIterator()
+    {
+        return $this->_messages;
+    }
+}

+ 145 - 0
library/Zend/Cloud/StorageService/Adapter.php

@@ -0,0 +1,145 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Common interface for unstructured cloud storage.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+interface Zend_Cloud_StorageService_Adapter
+{
+    // HTTP adapter to use for connections
+    const HTTP_ADAPTER = 'http_adapter';
+
+    /**
+     * Get an item from the storage service.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return mixed
+     */
+    public function fetchItem($path, $options = null);
+    
+    /**
+     * Store an item in the storage service.
+     * WARNING: This operation overwrites any item that is located at 
+     * $destinationPath.
+     * @param string $destinationPath
+     * @param mixed  $data
+     * @param  array $options
+     * @return boolean
+     */
+    public function storeItem($destinationPath,
+                              $data,
+                              $options = null);
+    
+    /**
+     * Delete an item in the storage service.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return void
+     */
+    public function deleteItem($path, $options = null);
+    
+    /**
+     * Copy an item in the storage service to a given path.
+     * 
+     * The $destinationPath must be a directory.
+     *
+     * @param  string $sourcePath
+     * @param  string $destination path
+     * @param  array $options
+     * @return void
+     */
+    public function copyItem($sourcePath, $destinationPath, $options = null);
+    
+    /**
+     * Move an item in the storage service to a given path.
+     * 
+     * The $destinationPath must be a directory.
+     *
+     * @param  string $sourcePath
+     * @param  string $destination path
+     * @param  array $options
+     * @return void
+     */
+    public function moveItem($sourcePath, $destinationPath, $options = null);
+    
+    /**
+     * Rename an item in the storage service to a given name.
+     *
+     *
+     * @param  string $path
+     * @param  string $name
+     * @param  array $options
+     * @return void
+     */
+    public function renameItem($path, $name, $options = null);
+    
+    /**
+     * List items in the given directory in the storage service
+     * 
+     * The $path must be a directory
+     *
+     *
+     * @param  string $path Must be a directory
+     * @param  array $options
+     * @return array A list of item names
+     */
+    public function listItems($path, $options = null);
+    
+    /**
+     * Get a key/value array of metadata for the given path.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return array
+     */
+    public function fetchMetadata($path, $options = null);
+    
+    /**
+     * Store a key/value array of metadata at the given path.
+     * WARNING: This operation overwrites any metadata that is located at 
+     * $destinationPath.
+     *
+     * @param  string $destinationPath
+     * @param  array $options
+     * @return void
+     */
+    public function storeMetadata($destinationPath, $metadata, $options = null);
+    
+    /**
+     * Delete a key/value array of metadata at the given path.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return void
+     */
+    public function deleteMetadata($path);
+
+    /**
+     * Get the concrete client.
+     */
+    public function getClient();
+}

+ 267 - 0
library/Zend/Cloud/StorageService/Adapter/FileSystem.php

@@ -0,0 +1,267 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/StorageService/Adapter.php';
+require_once 'Zend/Cloud/StorageService/Exception.php';
+
+/**
+ * FileSystem adapter for unstructured cloud storage.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_FileSystem implements Zend_Cloud_StorageService_Adapter
+{
+
+    /**
+     * Options array keys for the file system adapter.
+     */
+    const LOCAL_DIRECTORY = 'local_directory';
+
+    /**
+     * The directory for the data
+     * @var string
+     */
+    protected $_directory = null;
+
+    /**
+     * Constructor
+     * 
+     * @param  array|Zend_Config $options 
+     * @return void
+     */
+    public function __construct($options = array()) 
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_StorageService_Exception('Invalid options provided');
+        }
+
+        if (isset($options[self::LOCAL_DIRECTORY])) {
+            $this->_directory = $options[self::LOCAL_DIRECTORY];
+        } else {
+            $this->_directory = realpath(sys_get_temp_dir());
+        }
+    }
+
+    /**
+     * Get an item from the storage service.
+     *
+     * TODO: Support streaming
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return false|string
+     */
+    public function fetchItem($path, $options = array()) 
+    {
+        $filepath = $this->_getFullPath($path);
+        $path     = realpath($filepath);
+
+        if (!$path) {
+            return false;
+        }
+
+        return file_get_contents($path);
+    }
+
+    /**
+     * Store an item in the storage service.
+     *
+     * WARNING: This operation overwrites any item that is located at
+     * $destinationPath.
+     *
+     * @TODO Support streams
+     *
+     * @param  string $destinationPath
+     * @param  mixed $data
+     * @param  array $options
+     * @return void
+     */
+    public function storeItem($destinationPath, $data, $options = array()) 
+    {
+        $path = $this->_getFullPath($destinationPath);
+        file_put_contents($path, $data);
+        chmod($path, 0777);
+    }
+
+    /**
+     * Delete an item in the storage service.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return void
+     */
+    public function deleteItem($path, $options = array()) 
+    {
+        if (!isset($path)) {
+            return;
+        }
+
+        $filepath = $this->_getFullPath($path);
+        if (file_exists($filepath)) {
+            unlink($filepath);
+        }
+    }
+
+    /**
+     * Copy an item in the storage service to a given path.
+     *
+     * WARNING: This operation is *very* expensive for services that do not
+     * support copying an item natively.
+     *
+     * @TODO Support streams for those services that don't support natively
+     *
+     * @param  string $sourcePath
+     * @param  string $destination path
+     * @param  array $options
+     * @return void
+     */
+    public function copyItem($sourcePath, $destinationPath, $options = array()) 
+    {
+        copy($this->_getFullPath($sourcePath), $this->_getFullPath($destinationPath));
+    }
+
+    /**
+     * Move an item in the storage service to a given path.
+     *
+     * WARNING: This operation is *very* expensive for services that do not
+     * support moving an item natively.
+     *
+     * @TODO Support streams for those services that don't support natively
+     *
+     * @param  string $sourcePath
+     * @param  string $destination path
+     * @param  array $options
+     * @return void
+     */
+    public function moveItem($sourcePath, $destinationPath, $options = array()) 
+    {
+        rename($this->_getFullPath($sourcePath), $this->_getFullPath($destinationPath));
+    }
+
+        /**
+     * Rename an item in the storage service to a given name.
+     *
+     *
+     * @param  string $path
+     * @param  string $name
+     * @param  array $options
+     * @return void
+     */
+    public function renameItem($path, $name, $options = null) 
+    {
+        rename(
+            $this->_getFullPath($path), 
+            dirname($this->_getFullPath($path)) . DIRECTORY_SEPARATOR . $name
+        );
+    }
+
+    /**
+     * List items in the given directory in the storage service
+     *
+     * The $path must be a directory
+     *
+     *
+     * @param  string $path Must be a directory
+     * @param  array $options
+     * @return array A list of item names
+     */
+    public function listItems($path, $options = null) 
+    {
+        $listing = scandir($this->_getFullPath($path));
+
+        // Remove the hidden navigation directories
+        $listing = array_diff($listing, array('.', '..'));
+
+        return $listing;
+    }
+
+    /**
+     * Get a key/value array of metadata for the given path.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return array
+     */
+    public function fetchMetadata($path, $options = array()) 
+    {
+        $fullPath = $this->_getFullPath($path);
+        $metadata = null;
+        if (file_exists($fullPath)) {
+            $metadata = stat(realpath($fullPath));
+        }
+
+        return isset($metadata) ? $metadata : false;
+    }
+
+    /**
+     * Store a key/value array of metadata at the given path.
+     * WARNING: This operation overwrites any metadata that is located at
+     * $destinationPath.
+     *
+     * @param  string $destinationPath
+     * @param  array $options
+     * @return void
+     */
+    public function storeMetadata($destinationPath, $metadata, $options = array()) 
+    {
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('Storing metadata not implemented');
+    }
+
+    /**
+     * Delete a key/value array of metadata at the given path.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return void
+     */
+    public function deleteMetadata($path) 
+    {
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('Deleting metadata not implemented');
+    }
+
+    /**
+     * Return the full path for the file.
+     * 
+     * @param string $path
+     * @return string
+     */
+    private function _getFullPath($path) 
+    {
+        return $this->_directory . DIRECTORY_SEPARATOR . $path;
+    }
+
+    /**
+     * Get the concrete client.
+     * @return strings
+     */
+    public function getClient()
+    {
+         return $this->_directory;       
+    }
+}

+ 399 - 0
library/Zend/Cloud/StorageService/Adapter/Nirvanix.php

@@ -0,0 +1,399 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/StorageService/Adapter.php';
+require_once 'Zend/Cloud/StorageService/Exception.php';
+require_once 'Zend/Service/Nirvanix.php';
+
+/**
+ * Adapter for Nirvanix cloud storage
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_Nirvanix 
+    implements Zend_Cloud_StorageService_Adapter
+{
+    const USERNAME         = 'auth_username';
+    const PASSWORD         = 'auth_password';
+    const APP_KEY          = 'auth_accesskey';
+    const REMOTE_DIRECTORY = 'remote_directory';
+
+    /**
+     * The Nirvanix adapter
+     * @var Zend_Service_Nirvanix
+     */
+    protected $_nirvanix;
+    protected $_imfNs;
+    protected $_metadataNs;
+    protected $_remoteDirectory;
+    private $maxPageSize = 500;
+
+    /**
+     * Constructor
+     * 
+     * @param  array|Zend_Config $options 
+     * @return void
+     */
+    function __construct($options = array()) 
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_StorageService_Exception('Invalid options provided');
+        }
+
+        $auth = array(
+            'username' => $options[self::USERNAME],
+            'password' => $options[self::PASSWORD],
+            'appKey'   => $options[self::APP_KEY],
+        );
+        $nirvanix_options = array();
+        if (isset($options[self::HTTP_ADAPTER])) {
+            $httpc = new Zend_Http_Client();
+            $httpc->setAdapter($options[self::HTTP_ADAPTER]);
+            $nirvanix_options['httpClient'] = $httpc;
+        } 
+        try {
+            $this->_nirvanix = new Zend_Service_Nirvanix($auth, $nirvanix_options);
+            $this->_remoteDirectory = $options[self::REMOTE_DIRECTORY];
+            $this->_imfNs = $this->_nirvanix->getService('IMFS');
+            $this->_metadataNs = $this->_nirvanix->getService('Metadata');
+        } catch (Zend_Service_Nirvanix_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+     /**
+     * Get an item from the storage service.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return mixed
+     */
+    public function fetchItem($path, $options = null)
+    {
+        $path = $this->_getFullPath($path);
+        try {
+            $item = $this->_imfNs->getContents($path);
+        } catch (Zend_Service_Nirvanix_Exception $e) {
+            throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e);
+        }
+        return $item;
+    }
+
+    /**
+     * Store an item in the storage service.
+     * WARNING: This operation overwrites any item that is located at
+     * $destinationPath.
+     * @param string $destinationPath
+     * @param mixed $data
+     * @param  array $options
+     * @return void
+     */
+    public function storeItem($destinationPath, $data, $options = null)
+    {
+        try {
+            $path = $this->_getFullPath($destinationPath);
+            $this->_imfNs->putContents($path, $data);
+        } catch (Zend_Service_Nirvanix_Exception $e) {
+            throw new Zend_Cloud_StorageService_Exception('Error on store: '.$e->getMessage(), $e->getCode(), $e);
+        }
+        return true;
+    }
+
+    /**
+     * Delete an item in the storage service.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return void
+     */
+    public function deleteItem($path, $options = null)
+    {
+        try {
+            $path = $this->_getFullPath($path);
+            $this->_imfNs->unlink($path);
+        } catch(Zend_Service_Nirvanix_Exception $e) {
+//            if (trim(strtoupper($e->getMessage())) != 'INVALID PATH') {
+//                // TODO Differentiate among errors in the Nirvanix adapter
+            throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Copy an item in the storage service to a given path.
+     * WARNING: This operation is *very* expensive for services that do not
+     * support copying an item natively.
+     *
+     * @param  string $sourcePath
+     * @param  string $destination path
+     * @param  array $options
+     * @return void
+     */
+    public function copyItem($sourcePath, $destinationPath, $options = null)
+    {
+        try {
+            $sourcePath = $this->_getFullPath($sourcePath);
+            $destinationPath = $this->_getFullPath($destinationPath);
+            $this->_imfNs->CopyFiles(array('srcFilePath' => $sourcePath,
+                                            'destFolderPath' => $destinationPath));
+        } catch (Zend_Service_Nirvanix_Exception $e) {
+            throw new Zend_Cloud_StorageService_Exception('Error on copy: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Move an item in the storage service to a given path.
+     * WARNING: This operation is *very* expensive for services that do not
+     * support moving an item natively.
+     *
+     * @param  string $sourcePath
+     * @param  string $destination path
+     * @param  array $options
+     * @return void
+     */
+    public function moveItem($sourcePath, $destinationPath, $options = null)
+    {
+        try {
+            $sourcePath = $this->_getFullPath($sourcePath);
+            $destinationPath = $this->_getFullPath($destinationPath);
+            $this->_imfNs->RenameFile(array('filePath' => $sourcePath,
+                                             'newFileName' => $destinationPath));
+    //        $this->_imfNs->MoveFiles(array('srcFilePath' => $sourcePath,
+    //                                         'destFolderPath' => $destinationPath));
+        } catch (Zend_Service_Nirvanix_Exception $e) {
+            throw new Zend_Cloud_StorageService_Exception('Error on move: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Rename an item in the storage service to a given name.
+     *
+     *
+     * @param  string $path
+     * @param  string $name
+     * @param  array $options
+     * @return void
+     */
+    public function renameItem($path, $name, $options = null) 
+    {
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('Renaming not implemented');
+    }
+
+    /**
+     * Get a key/value array of metadata for the given path.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return array An associative array of key/value pairs specifying the metadata for this object.
+     *                  If no metadata exists, an empty array is returned.
+     */
+    public function fetchMetadata($path, $options = null)
+    {
+        $path = $this->_getFullPath($path);
+        try {
+            $metadataNode = $this->_metadataNs->getMetadata(array('path' => $path));
+        } catch (Zend_Service_Nirvanix_Exception $e) {
+            throw new Zend_Cloud_StorageService_Exception('Error on fetching metadata: '.$e->getMessage(), $e->getCode(), $e);
+        }
+            
+        $metadata = array();
+        $length = count($metadataNode->Metadata);
+
+        // Need to special case this as Nirvanix returns an array if there is
+        // more than one, but doesn't return an array if there is only one.
+        if ($length == 1)
+        {
+            $metadata[(string)$metadataNode->Metadata->Type->value] = (string)$metadataNode->Metadata->Value;
+        }
+        else if ($length > 1)
+        {
+            for ($i=0; $i<$length; $i++)
+            {
+                $metadata[(string)$metadataNode->Metadata[$i]->Type] = (string)$metadataNode->Metadata[$i]->Value;
+            }
+        }
+        return $metadata;
+    }
+
+    /**
+     * Store a key/value array of metadata at the given path.
+     * WARNING: This operation overwrites any metadata that is located at
+     * $destinationPath.
+     *
+     * @param array $metadata - An associative array specifying the key/value pairs for the metadata.
+     * @param $destinationPath
+     * @param  array $options
+     * @return void
+     */
+    public function storeMetadata($destinationPath, $metadata, $options = null)
+    {
+        $destinationPath = $this->_getFullPath($destinationPath);
+        if ($metadata != null) {
+            try {
+                foreach ($metadata AS $key=>$value) {
+                    $metadataString = $key . ":" . $value;
+                    $this->_metadataNs->SetMetadata(array(
+                        'path'     => $destinationPath,
+                        'metadata' => $metadataString,
+                    ));
+                }
+            } catch (Zend_Service_Nirvanix_Exception $e) {
+                throw new Zend_Cloud_StorageService_Exception('Error on storing metadata: '.$e->getMessage(), $e->getCode(), $e);
+            }
+        }
+     }
+
+    /**
+     * Delete a key/value array of metadata at the given path.
+     *
+     * @param string $path
+     * @param array $metadata - An associative array specifying the key/value pairs for the metadata
+     *                          to be deleted.  If null, all metadata associated with the object will
+     *                          be deleted.
+     * @param  array $options
+     * @return void
+     */
+    public function deleteMetadata($path, $metadata = null, $options = null)
+    {
+        $path = $this->_getFullPath($path);
+        try {
+            if ($metadata == null) {
+                $this->_metadataNs->DeleteAllMetadata(array('path' => $path));
+            } else {
+                foreach ($metadata AS $key=>$value) {
+                    $this->_metadataNs->DeleteMetadata(array(
+                        'path'     => $path,
+                        'metadata' => $key,
+                    ));
+                    }
+            }
+        } catch (Zend_Service_Nirvanix_Exception $e) {
+            throw new Zend_Cloud_StorageService_Exception('Error on deleting metadata: '.$e->getMessage(), $e->getCode(), $e);
+        }            
+    }
+
+    /*
+     * Recursively traverse all the folders and build an array that contains
+     * the path names for each folder.
+     *
+     * @param $path - The folder path to get the list of folders from.
+     * @param &$resultArray - reference to the array that contains the path names
+     *                           for each folder.
+     */
+    private function getAllFolders($path, &$resultArray)
+    {
+        $response = $this->_imfNs->ListFolder(array(
+            'folderPath' => $path,
+               'pageNumber' => 1,
+            'pageSize'   => $this->maxPageSize,
+        ));
+           $numFolders = $response->ListFolder->TotalFolderCount;
+           if ($numFolders == 0) {
+               return;
+           } else {
+               //Need to special case this as Nirvanix returns an array if there is
+               //more than one, but doesn't return an array if there is only one.
+            if ($numFolders == 1) {
+                $folderPath = $response->ListFolder->Folder->Path;
+                array_push($resultArray, $folderPath);
+                $this->getAllFolders('/' . $folderPath, $resultArray);
+            } else {
+                foreach ($response->ListFolder->Folder as $arrayElem) {
+                    $folderPath = $arrayElem->Path;
+                    array_push($resultArray, $folderPath);
+                    $this->getAllFolders('/' . $folderPath, $resultArray);
+                }
+            }
+           }
+    }
+
+    /**
+     * Return an array of the items contained in the given path.  The items
+     * returned are the files or objects that in the specified path.
+     *
+     * @param  string $path
+     * @param  array  $options
+     * @return array
+     */
+    public function listItems($path, $options = null)
+    {
+        $path = $this->_getFullPath($path);
+        $resultArray = array();
+
+        if (!isset($path)) {
+            return false;
+        } else {
+            try {
+                $response = $this->_imfNs->ListFolder(array(
+                    'folderPath' => $path,
+                    'pageNumber' => 1,
+                    'pageSize'   => $this->maxPageSize,
+                ));
+            } catch (Zend_Service_Nirvanix_Exception $e) {
+                throw new Zend_Cloud_StorageService_Exception('Error on list: '.$e->getMessage(), $e->getCode(), $e);
+            }            
+                
+            $numFiles = $response->ListFolder->TotalFileCount;
+
+            //Add the file names to the array
+            if ($numFiles != 0) {
+                //Need to special case this as Nirvanix returns an array if there is
+                //more than one, but doesn't return an array if there is only one.
+                if ($numFiles == 1) {
+                    $resultArray[] = (string)$response->ListFolder->File->Name;
+                }
+                else {
+                    foreach ($response->ListFolder->File as $arrayElem) {
+                        $resultArray[] = (string) $arrayElem->Name;
+                    }
+                }
+            }
+        }
+
+        return $resultArray;
+    }
+
+    /**
+     * Get full path to an object
+     * 
+     * @param  string $path 
+     * @return string
+     */
+    private function _getFullPath($path) 
+    {
+        return $this->_remoteDirectory . $path;
+    }
+
+    /**
+     * Get the concrete client.
+     * @return Zend_Service_Nirvanix
+     */
+    public function getClient()
+    {
+         return $this->_nirvanix;       
+    }
+}

+ 327 - 0
library/Zend/Cloud/StorageService/Adapter/S3.php

@@ -0,0 +1,327 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Service/Amazon/S3.php';
+require_once 'Zend/Cloud/StorageService/Adapter.php';
+require_once 'Zend/Cloud/StorageService/Exception.php';
+
+/**
+ * S3 adapter for unstructured cloud storage.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_S3 
+    implements Zend_Cloud_StorageService_Adapter
+{
+    /*
+     * Options array keys for the S3 adapter.
+     */
+    const BUCKET_NAME      = 'bucket_name';
+    const BUCKET_AS_DOMAIN = 'bucket_as_domain?';
+    const FETCH_STREAM     = 'fetch_stream';
+    const METADATA         = 'metadata';
+    
+    /**
+     * AWS constants
+     */
+    const AWS_ACCESS_KEY   = 'aws_accesskey';
+    const AWS_SECRET_KEY   = 'aws_secretkey';
+
+    /**
+     * S3 service instance.
+     * @var Zend_Service_Amazon_S3
+     */
+    protected $_s3;
+    protected $_defaultBucketName = null;
+    protected $_defaultBucketAsDomain = false;
+
+    /**
+     * Constructor
+     * 
+     * @param  array|Zend_Config $options 
+     * @return void
+     */
+    public function __construct($options = array()) 
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_StorageService_Exception('Invalid options provided');
+        }
+
+        if (!isset($options[self::AWS_ACCESS_KEY]) || !isset($options[self::AWS_SECRET_KEY])) {
+            throw new Zend_Cloud_StorageService_Exception('AWS keys not specified!');
+        }
+
+        try {
+            $this->_s3 = new Zend_Service_Amazon_S3($options[self::AWS_ACCESS_KEY],
+                                                $options[self::AWS_SECRET_KEY]);
+        } catch (Zend_Service_Amazon_S3_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e);
+        }
+                                                
+        if (isset($options[self::HTTP_ADAPTER])) {
+            $this->_s3->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]);
+        } 
+
+        if (isset($options[self::BUCKET_NAME])) {
+            $this->_defaultBucketName = $options[self::BUCKET_NAME];
+        }
+
+        if (isset($options[self::BUCKET_AS_DOMAIN])) {
+            $this->_defaultBucketAsDomain = $options[self::BUCKET_AS_DOMAIN];
+        }
+    }
+
+    /**
+     * Get an item from the storage service.
+     *
+     * @TODO Support streams
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return string
+     */
+    public function fetchItem($path, $options = array()) 
+    {
+        $fullPath = $this->_getFullPath($path, $options);
+        try {
+            if (!empty($options[self::FETCH_STREAM])) {
+                return $this->_s3->getObjectStream($fullPath, $options[self::FETCH_STREAM]);
+            } else {
+                return $this->_s3->getObject($fullPath);
+            }
+        } catch (Zend_Service_Amazon_S3_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Store an item in the storage service.
+     *
+     * WARNING: This operation overwrites any item that is located at
+     * $destinationPath.
+     *
+     * @TODO Support streams
+     *
+     * @param string $destinationPath
+     * @param string|resource $data
+     * @param  array $options
+     * @return void
+     */
+    public function storeItem($destinationPath, $data, $options = array()) 
+    {
+        try {
+            $fullPath = $this->_getFullPath($destinationPath, $options);
+            return $this->_s3->putObject(
+                $fullPath, 
+                $data, 
+                empty($options[self::METADATA]) ? null : $options[self::METADATA]
+            );
+        } catch (Zend_Service_Amazon_S3_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on store: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Delete an item in the storage service.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return void
+     */
+    public function deleteItem($path, $options = array()) 
+    {    
+        try {
+            $this->_s3->removeObject($this->_getFullPath($path, $options));
+        } catch (Zend_Service_Amazon_S3_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Copy an item in the storage service to a given path.
+     *
+     * WARNING: This operation is *very* expensive for services that do not
+     * support copying an item natively.
+     *
+     * @TODO Support streams for those services that don't support natively
+     *
+     * @param  string $sourcePath
+     * @param  string $destination path
+     * @param  array $options
+     * @return void
+     */
+    public function copyItem($sourcePath, $destinationPath, $options = array()) 
+    {
+        try {
+            // TODO We *really* need to add support for object copying in the S3 adapter
+            $item = $this->fetch($_getFullPath(sourcePath), $options);
+            $this->storeItem($item, $destinationPath, $options);
+        } catch (Zend_Service_Amazon_S3_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on copy: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Move an item in the storage service to a given path.
+     *
+     * WARNING: This operation is *very* expensive for services that do not
+     * support moving an item natively.
+     *
+     * @TODO Support streams for those services that don't support natively
+     *
+     * @param  string $sourcePath
+     * @param  string $destination path
+     * @param  array $options
+     * @return void
+     */
+    public function moveItem($sourcePath, $destinationPath, $options = array()) 
+    {
+        try {
+            // TODO We *really* need to add support for object copying in the S3 adapter
+            $item = $this->fetch($sourcePath, $options);
+            $this->storeItem($item, $destinationPath, $options);
+            $this->deleteItem($sourcePath, $options);
+        } catch (Zend_Service_Amazon_S3_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on move: '.$e->getMessage(), $e->getCode(), $e);
+        }
+     }
+
+    /**
+     * Rename an item in the storage service to a given name.
+     *
+     *
+     * @param  string $path
+     * @param  string $name
+     * @param  array $options
+     * @return void
+     */
+    public function renameItem($path, $name, $options = null) 
+    {
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('Rename not implemented');
+    }
+
+    /**
+     * List items in the given directory in the storage service
+     *
+     * The $path must be a directory
+     *
+     *
+     * @param  string $path Must be a directory
+     * @param  array $options
+     * @return array A list of item names
+     */
+    public function listItems($path, $options = null) 
+    {
+        try {
+            // TODO Support 'prefix' parameter for Zend_Service_Amazon_S3::getObjectsByBucket()
+            return $this->_s3->getObjectsByBucket($this->_defaultBucketName);
+        } catch (Zend_Service_Amazon_S3_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on list: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Get a key/value array of metadata for the given path.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return array
+     */
+    public function fetchMetadata($path, $options = array()) 
+    {
+        try {
+            return $this->_s3->getInfo($this->_getFullPath($path, $options));
+        } catch (Zend_Service_Amazon_S3_Exception  $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Store a key/value array of metadata at the given path.
+     * WARNING: This operation overwrites any metadata that is located at
+     * $destinationPath.
+     *
+     * @param  string $destinationPath
+     * @param  array $options
+     * @return void
+     */
+    public function storeMetadata($destinationPath, $metadata, $options = array()) 
+    {
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('Storing separate metadata is not supported, use storeItem() with \'metadata\' option key');
+    }
+
+    /**
+     * Delete a key/value array of metadata at the given path.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return void
+     */
+    public function deleteMetadata($path) 
+    {
+        require_once 'Zend/Cloud/OperationNotAvailableException.php';
+        throw new Zend_Cloud_OperationNotAvailableException('Deleting metadata not supported');
+    }
+
+    /**
+     * Get full path, including bucket, for an object
+     * 
+     * @param  string $path 
+     * @param  array $options 
+     * @return void
+     */
+    protected function _getFullPath($path, $options) 
+    {
+        if (isset($options[self::BUCKET_NAME])) {
+            $bucket = $options[self::BUCKET_NAME];
+        } else if (isset($this->_defaultBucketName)) {
+            $bucket = $this->_defaultBucketName;
+        } else {
+            require_once 'Zend/Cloud/StorageService/Exception.php';
+            throw new Zend_Cloud_StorageService_Exception('Bucket name must be specified for S3 adapter.');
+        }
+
+        if (isset($options[self::BUCKET_AS_DOMAIN])) {
+            // TODO: support bucket domain names
+            require_once 'Zend/Cloud/StorageService/Exception.php';
+            throw new Zend_Cloud_StorageService_Exception('The S3 adapter does not currently support buckets in domain names.');
+        }
+
+        return trim($bucket) . '/' . trim($path);
+    }
+
+    /**
+     * Get the concrete client.
+     * @return Zend_Service_Amazon_S3
+     */
+    public function getClient()
+    {
+         return $this->_s3;       
+    }
+}

+ 443 - 0
library/Zend/Cloud/StorageService/Adapter/WindowsAzure.php

@@ -0,0 +1,443 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/StorageService/Adapter.php';
+require_once 'Zend/Service/WindowsAzure/Storage/Blob.php';
+require_once 'Zend/Cloud/StorageService/Exception.php';
+
+/**
+ *
+ * Windows Azure Blob Service abstraction
+ * 
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_WindowsAzure 
+    implements Zend_Cloud_StorageService_Adapter
+{
+    const ACCOUNT_NAME      = 'storage_accountname';
+    const ACCOUNT_KEY       = 'storage_accountkey';
+    const HOST              = "storage_host";
+    const PROXY_HOST        = "storage_proxy_host";
+    const PROXY_PORT        = "storage_proxy_port";
+    const PROXY_CREDENTIALS = "storage_proxy_credentials";
+    const CONTAINER         = "storage_container";
+    const RETURN_TYPE       = 'return_type';
+    const RETURN_PATHNAME   = 'return_path';
+    const RETURN_OPENMODE   = 'return_openmode';
+    
+    /** return types  for fetch */
+    const RETURN_PATH   = 1;   // return filename
+    const RETURN_STRING = 2; // return data as string
+    const RETURN_STREAM = 3; // return PHP stream
+
+    /** return types  for list */
+    const RETURN_LIST  = 1;   // return native list
+    const RETURN_NAMES = 2;  // return only names
+    
+    const DEFAULT_HOST = Zend_Service_WindowsAzure_Storage::URL_CLOUD_BLOB;
+
+    /**
+     * Storage container to operate on
+     * 
+     * @var string
+     */
+    protected $_container;
+    
+    /**
+     * Storage client
+     * 
+     * @var Zend_Service_WindowsAzure_Storage_Blob
+     */
+    protected $_storageClient = null;
+
+    /**
+     * Creates a new Zend_Cloud_Storage_WindowsAzure instance
+     * 
+     * @param array|Zend_Config  $options   Options for the Zend_Cloud_Storage_WindowsAzure instance
+     */
+    public function __construct($options = array())
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        if (!is_array($options)) {
+            throw new Zend_Cloud_StorageService_Exception('Invalid options provided');
+        }
+
+        // Build Zend_Service_WindowsAzure_Storage_Blob instance
+        if (!isset($options[self::HOST])) {
+            $host = self::DEFAULT_HOST;
+        } else {
+            $host = $options[self::HOST];
+        }
+        
+        if (!isset($options[self::ACCOUNT_NAME])) {
+            throw new Zend_Cloud_StorageService_Exception('No Windows Azure account name provided.');
+        }
+        if (!isset($options[self::ACCOUNT_KEY])) {
+            throw new Zend_Cloud_StorageService_Exception('No Windows Azure account key provided.');
+        }
+            
+        $this->_storageClient = new Zend_Service_WindowsAzure_Storage_Blob($host,
+             $options[self::ACCOUNT_NAME], $options[self::ACCOUNT_KEY]);
+        
+        // Parse other options
+        if (!empty($options[self::PROXY_HOST])) {
+            $proxyHost = $options[self::PROXY_HOST];
+            $proxyPort = isset($options[self::PROXY_PORT]) ? $options[self::PROXY_PORT] : 8080;
+            $proxyCredentials = isset($options[self::PROXY_CREDENTIALS]) ? $options[self::PROXY_CREDENTIALS] : '';
+            
+            $this->_storageClient->setProxy(true, $proxyHost, $proxyPort, $proxyCredentials);
+        }
+        
+        if (isset($options[self::HTTP_ADAPTER])) {
+            $this->_storageClient->setHttpClientChannel($options[self::HTTP_ADAPTER]);
+        }
+        
+        // Set container
+        $this->_container = $options[self::CONTAINER];
+
+        // Make sure the container exists
+        if (!$this->_storageClient->containerExists($this->_container)) {
+            $this->_storageClient->createContainer($this->_container);
+        }
+    }
+    
+    /**
+     * Get an item from the storage service.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return mixed
+     */
+    public function fetchItem($path, $options = null)
+    {
+        // Options
+        $returnType = self::RETURN_STRING; 
+        $returnPath = tempnam('', 'azr');
+        $openMode   = 'r';
+        
+        // Parse options
+        if (is_array($options)) {
+            if (isset($options[self::RETURN_TYPE])) {
+                $returnType = $options[self::RETURN_TYPE];
+            }
+                
+            if (isset($options[self::RETURN_PATHNAME])) {
+                $returnPath = $options[self::RETURN_PATHNAME];
+            }
+                
+            if (isset($options[self::RETURN_OPENMODE])) {
+                $openMode = $options[self::RETURN_OPENMODE];
+            }
+        }
+        
+        // Fetch the blob
+        try {
+            $this->_storageClient->getBlob(
+                $this->_container,
+                $path,
+                $returnPath
+            );
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            if (strpos($e->getMessage(), "does not exist") !== false) {
+                return false;
+            }
+            throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e);
+        }
+        
+        // Return value
+        if ($returnType == self::RETURN_PATH) {
+            return $returnPath;
+        }
+        if ($returnType == self::RETURN_STRING) {
+            return file_get_contents($returnPath);
+        }
+        if ($returnType == self::RETURN_STREAM) {
+            return fopen($returnPath, $openMode);
+        }
+    }
+    
+    /**
+     * Store an item in the storage service.
+     * WARNING: This operation overwrites any item that is located at 
+     * $destinationPath.
+     * @param string $destinationPath
+     * @param mixed  $data
+     * @param  array $options
+     * @return boolean
+     */
+    public function storeItem($destinationPath, $data, $options = null) 
+    {
+        // Create a temporary file that will be uploaded
+        $temporaryFilePath       = '';
+        $removeTemporaryFilePath = false;
+
+        if (is_resource($data))    {
+            $temporaryFilePath = tempnam('', 'azr');
+            $fpDestination     = fopen($temporaryFilePath, 'w');
+
+            $fpSource = $data;
+            rewind($fpSource);
+            while (!feof($fpSource)) {
+                fwrite($fpDestination, fread($fpSource, 8192));
+            }
+            
+            fclose($fpDestination);
+            
+            $removeTemporaryFilePath = true;
+        } elseif (file_exists($data)) {
+            $temporaryFilePath       = $data;
+            $removeTemporaryFilePath = false;
+        } else {
+            $temporaryFilePath = tempnam('', 'azr');
+            file_put_contents($temporaryFilePath, $data);
+            $removeTemporaryFilePath = true;
+        }
+        
+        try {
+            // Upload data
+            $this->_storageClient->putBlob(
+                $this->_container,
+                $destinationPath,
+                $temporaryFilePath
+            );
+        } catch(Zend_Service_WindowsAzure_Exception $e) {
+            @unlink($temporaryFilePath);
+            throw new Zend_Cloud_StorageService_Exception('Error on store: '.$e->getMessage(), $e->getCode(), $e);        
+        }
+        if ($removeTemporaryFilePath) {
+            @unlink($temporaryFilePath);
+        }
+    }
+    
+    /**
+     * Delete an item in the storage service.
+     *
+     * @param  string $path
+     * @param  array  $options
+     * @return void
+     */
+    public function deleteItem($path, $options = null)
+    {
+        try {
+            $this->_storageClient->deleteBlob(
+                $this->_container,
+                $path
+            );
+        } catch (Zend_Service_WindowsAzure_Exception $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Copy an item in the storage service to a given path.
+     *
+     * @param  string $sourcePath
+     * @param  string $destinationPath
+     * @param  array  $options
+     * @return void
+     */
+    public function copyItem($sourcePath, $destinationPath, $options = null)
+    {
+        try {
+            $this->_storageClient->copyBlob(
+                $this->_container,
+                $sourcePath,
+                $this->_container,
+                $destinationPath
+            );
+        } catch (Zend_Service_WindowsAzure_Exception $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on copy: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Move an item in the storage service to a given path.
+     *
+     * @param  string $sourcePath
+     * @param  string $destinationPath
+     * @param  array  $options
+     * @return void
+     */
+    public function moveItem($sourcePath, $destinationPath, $options = null)
+    {
+        try {
+            $this->_storageClient->copyBlob(
+                $this->_container,
+                $sourcePath,
+                $this->_container,
+                $destinationPath
+            );
+            
+            $this->_storageClient->deleteBlob(
+                $this->_container,
+                $sourcePath
+            );
+        } catch (Zend_Service_WindowsAzure_Exception $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on move: '.$e->getMessage(), $e->getCode(), $e);
+        }
+            
+    }
+    
+    /**
+     * Rename an item in the storage service to a given name.
+     *
+     *
+     * @param  string $path
+     * @param  string $name
+     * @param  array $options
+     * @return void
+     */
+    public function renameItem($path, $name, $options = null)
+    {
+        return $this->moveItem($path, $name, $options);
+    }
+    
+    /**
+     * List items in the given directory in the storage service
+     * 
+     * The $path must be a directory
+     *
+     *
+     * @param  string $path Must be a directory
+     * @param  array $options
+     * @return array A list of item names
+     */
+    public function listItems($path, $options = null)
+    {
+        // Options
+        $returnType = self::RETURN_NAMES; // 1: return list of paths, 2: return raw output from underlying provider
+        
+        // Parse options
+        if (is_array($options)&& isset($options[self::RETURN_TYPE])) {
+               $returnType = $options[self::RETURN_TYPE];
+        }
+        
+        try {
+            // Fetch list
+            $blobList = $this->_storageClient->listBlobs(
+                $this->_container,
+                $path
+            );
+        } catch (Zend_Service_WindowsAzure_Exception $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on list: '.$e->getMessage(), $e->getCode(), $e);
+        }
+        
+        // Return
+        if ($returnType == self::RETURN_LIST) {
+            return $blobList;
+        }
+        
+        $returnValue = array();
+        foreach ($blobList as $blob) {
+            $returnValue[] = $blob->Name;
+        }
+            
+        return $returnValue;
+    }
+
+    /**
+     * Get a key/value array of metadata for the given path.
+     *
+     * @param  string $path
+     * @param  array  $options
+     * @return array
+     */
+    public function fetchMetadata($path, $options = null)
+    {
+        try {
+            return $this->_storageClient->getBlobMetaData(
+                $this->_container,
+                $path
+            );
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            if (strpos($e->getMessage(), "could not be accessed") !== false) {
+                return false;
+            }
+            throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+    
+    /**
+     * Store a key/value array of metadata at the given path.
+     * WARNING: This operation overwrites any metadata that is located at 
+     * $destinationPath.
+     *
+     * @param  string $destinationPath
+     * @param  array $options
+     * @return void
+     */
+    public function storeMetadata($destinationPath, $metadata, $options = null)
+    {
+        try    {
+            $this->_storageClient->setBlobMetadata($this->_container, $destinationPath, $metadata);
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            if (strpos($e->getMessage(), "could not be accessed") === false) {
+                throw new Zend_Cloud_StorageService_Exception('Error on store metadata: '.$e->getMessage(), $e->getCode(), $e);
+            }
+        }
+    }
+    
+    /**
+     * Delete a key/value array of metadata at the given path.
+     *
+     * @param  string $path
+     * @param  array $options
+     * @return void
+     */
+    public function deleteMetadata($path, $options = null)
+    {
+        try {
+            $this->_storageClient->setBlobMetadata($this->_container, $destinationPath, array());
+        } catch (Zend_Service_WindowsAzure_Exception $e) {
+            if (strpos($e->getMessage(), "could not be accessed") === false) {
+                throw new Zend_Cloud_StorageService_Exception('Error on delete metadata: '.$e->getMessage(), $e->getCode(), $e);
+            }
+        }
+    }
+    
+    /**
+     * Delete container
+     * 
+     * @return void
+     */
+    public function deleteContainer()
+    {
+        try {
+            $this->_storageClient->deleteContainer($this->_container);
+        } catch (Zend_Service_WindowsAzure_Exception $e) { 
+            throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Get the concrete adapter.
+     * @return Zend_Service_Azure_Storage_Blob
+     */
+    public function getClient()
+    {
+         return $this->_storageClient;       
+    }
+}

+ 38 - 0
library/Zend/Cloud/StorageService/Exception.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+/**
+ * Zend_Cloud_Exception
+ */
+require_once 'Zend/Cloud/Exception.php';
+
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Exception extends Zend_Cloud_Exception
+{}
+

+ 70 - 0
library/Zend/Cloud/StorageService/Factory.php

@@ -0,0 +1,70 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Cloud/AbstractFactory.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage StorageService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Factory extends Zend_Cloud_AbstractFactory
+{
+    const STORAGE_ADAPTER_KEY = 'storage_adapter';
+    
+    /**
+     * @var string Interface which adapter must implement to be considered valid
+     */
+    protected static $_adapterInterface = 'Zend_Cloud_StorageService_Adapter';
+    /**
+     * Constructor
+     * 
+     * @return void
+     */
+    private function __construct()
+    {
+        // private ctor - should not be used
+    }
+    
+    /**
+     * Retrieve StorageService adapter
+     * 
+     * @param  array $options 
+     * @return void
+     */
+    public static function getAdapter($options = array()) 
+    {
+        $adapter = parent::_getAdapter(self::STORAGE_ADAPTER_KEY, $options);
+        if (!$adapter) {
+            require_once 'Zend/Cloud/StorageService/Exception.php';
+            throw new Zend_Cloud_StorageService_Exception('Class must be specified using the \'' .
+            self::STORAGE_ADAPTER_KEY . '\' key');
+        } elseif (!$adapter instanceof self::$_adapterInterface) {
+            require_once 'Zend/Cloud/StorageService/Exception.php';
+            throw new Zend_Cloud_StorageService_Exception(
+                'Adapter must implement \'' . self::$_adapterInterface . '\''
+            );
+        }
+        return $adapter;
+    }
+}

+ 82 - 0
library/Zend/Service/Amazon/Authentication.php

@@ -0,0 +1,82 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Service_Amazon_Authentication
+{
+    protected $_accessKey;
+    protected $_secretKey;
+    protected $_apiVersion;
+    
+    /**
+     * Constructor
+     * 
+     * @param  string $accessKey 
+     * @param  string $secretKey 
+     * @param  string $apiVersion 
+     * @return void
+     */
+    public function __construct($accessKey, $secretKey, $apiVersion)
+    {
+        $this->setAccessKey($accessKey);
+        $this->setSecretKey($secretKey);
+        $this->setApiVersion($apiVersion);
+    }
+    
+    /**
+     * Set access key
+     * 
+     * @param  string $accessKey 
+     * @return void
+     */
+    public function setAccessKey($accessKey) 
+    {
+        $this->_accessKey = $accessKey;
+    }
+    
+    /**
+     * Set secret key
+     * 
+     * @param  string $secretKey 
+     * @return void
+     */
+    public function setSecretKey($secretKey) 
+    {
+        $this->_secretKey = $secretKey;
+    }
+    
+    /**
+     * Set API version
+     * 
+     * @param  string $apiVersion 
+     * @return void
+     */
+    public function setApiVersion($apiVersion) 
+    {
+        $this->_apiVersion = $apiVersion;
+    }
+}

+ 37 - 0
library/Zend/Service/Amazon/Authentication/Exception.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Service_Amazon_Exception
+ */
+require_once 'Zend/Service/Amazon/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_Authentication_Exception extends Zend_Service_Amazon_Exception
+{
+
+}

+ 112 - 0
library/Zend/Service/Amazon/Authentication/S3.php

@@ -0,0 +1,112 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+/**
+ * @see Zend_Service_Amazon_Authentication
+ */
+require_once 'Zend/Service/Amazon/Authentication.php';
+
+/**
+ * @see Zend_Crypt_Hmac
+ */
+require_once 'Zend/Crypt/Hmac.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_Authentication_S3 extends Zend_Service_Amazon_Authentication
+{
+    /**
+     * Add the S3 Authorization signature to the request headers
+     *
+     * @param  string $method
+     * @param  string $path
+     * @param  array &$headers
+     * @return string
+     */
+    public function generateSignature($method, $path, &$headers)
+    {
+        if (! is_array($headers)) {
+            $headers = array($headers);
+        }
+        
+        $type = $md5 = $date = '';
+        
+        // Search for the Content-type, Content-MD5 and Date headers
+        foreach ($headers as $key => $val) {
+            if (strcasecmp($key, 'content-type') == 0) {
+                $type = $val;
+            } else if (strcasecmp($key, 'content-md5') == 0) {
+                $md5 = $val;
+            } else if (strcasecmp($key, 'date') == 0) {
+                $date = $val;
+            }
+        }
+        
+        // If we have an x-amz-date header, use that instead of the normal Date
+        if (isset($headers['x-amz-date']) && isset($date)) {
+            $date = '';
+        }
+        
+        $sig_str = "$method\n$md5\n$type\n$date\n";
+
+        // For x-amz- headers, combine like keys, lowercase them, sort them
+        // alphabetically and remove excess spaces around values
+        $amz_headers = array();
+        foreach ($headers as $key => $val) {
+            $key = strtolower($key);
+            if (substr($key, 0, 6) == 'x-amz-') {
+                if (is_array($val)) {
+                    $amz_headers[$key] = $val;
+                } else {
+                    $amz_headers[$key][] = preg_replace('/\s+/', ' ', $val);
+                }
+            }
+        }
+        if (!empty($amz_headers)) {
+            ksort($amz_headers);
+            foreach ($amz_headers as $key => $val) {
+                $sig_str .= $key . ':' . implode(',', $val) . "\n";
+            }
+        }
+        
+        $sig_str .= '/'.parse_url($path, PHP_URL_PATH);
+        if (strpos($path, '?location') !== false) {
+            $sig_str .= '?location';
+        } else 
+            if (strpos($path, '?acl') !== false) {
+                $sig_str .= '?acl';
+            } else 
+                if (strpos($path, '?torrent') !== false) {
+                    $sig_str .= '?torrent';
+                }
+        
+        $signature = base64_encode(Zend_Crypt_Hmac::compute($this->_secretKey, 'sha1', utf8_encode($sig_str), Zend_Crypt_Hmac::BINARY));
+        $headers['Authorization'] = 'AWS ' . $this->_accessKey . ':' . $signature;
+
+        return $sig_str;
+    }
+}

+ 108 - 0
library/Zend/Service/Amazon/Authentication/V1.php

@@ -0,0 +1,108 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Service_Amazon_Authentication
+ */
+require_once 'Zend/Service/Amazon/Authentication.php';
+
+/**
+ * @see Zend_Crypt_Hmac
+ */
+require_once 'Zend/Crypt/Hmac.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_Authentication_V1 extends Zend_Service_Amazon_Authentication
+{
+    /**
+     * Signature Version
+     */
+    protected $_signatureVersion = '1';
+
+    /**
+     * Signature Encoding Method
+     */
+    protected $_signatureMethod = 'HmacSHA256';
+    
+    /**
+     * Generate the required attributes for the signature
+     * @param string $url
+     * @param array $parameters
+     * @return string
+     */
+    public function generateSignature($url, array &$parameters)
+    {
+        $parameters['AWSAccessKeyId']   = $this->_accessKey;
+        $parameters['SignatureVersion'] = $this->_signatureVersion;
+        $parameters['Version']          = $this->_apiVersion;
+        if(!isset($parameters['Timestamp'])) {
+            $parameters['Timestamp']    = gmdate('Y-m-d\TH:i:s\Z', time()+10);
+        }
+
+        $data = $this->_signParameters($url, $parameters);
+        
+        return $data;
+    }
+
+    /**
+     * Computes the RFC 2104-compliant HMAC signature for request parameters
+     *
+     * This implements the Amazon Web Services signature, as per the following
+     * specification:
+     *
+     * 1. Sort all request parameters (including <tt>SignatureVersion</tt> and
+     *    excluding <tt>Signature</tt>, the value of which is being created),
+     *    ignoring case.
+     *
+     * 2. Iterate over the sorted list and append the parameter name (in its
+     *    original case) and then its value. Do not URL-encode the parameter
+     *    values before constructing this string. Do not use any separator
+     *    characters when appending strings.
+     *
+     * @param  string $queue_url  Queue URL
+     * @param  array  $parameters the parameters for which to get the signature.
+     *
+     * @return string the signed data.
+     */
+    protected function _signParameters($url, array &$paramaters)
+    {
+        $data = '';
+
+        uksort($paramaters, 'strcasecmp');
+        unset($paramaters['Signature']);
+
+        foreach($paramaters as $key => $value) {
+            $data .= $key . $value;
+        }
+
+        $hmac = Zend_Crypt_Hmac::compute($this->_secretKey, 'SHA1', $data, Zend_Crypt_Hmac::BINARY);
+
+        $paramaters['Signature'] = base64_encode($hmac);
+        
+        return $data;
+    }
+}

+ 138 - 0
library/Zend/Service/Amazon/Authentication/V2.php

@@ -0,0 +1,138 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Service_Amazon_Authentication
+ */
+require_once 'Zend/Service/Amazon/Authentication.php';
+
+/**
+ * @see Zend_Crypt_Hmac
+ */
+require_once 'Zend/Crypt/Hmac.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Authentication
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_Authentication_V2 extends Zend_Service_Amazon_Authentication
+{
+    /**
+     * Signature Version
+     */
+    protected $_signatureVersion = '2';
+
+    /**
+     * Signature Encoding Method
+     */
+    protected $_signatureMethod = 'HmacSHA256';
+    
+    /**
+     * Type of http request
+     * @var string
+     */
+    protected $_httpMethod = "POST";
+
+    /**
+     * Generate the required attributes for the signature
+     * @param string $url
+     * @param array $parameters
+     * @return string
+     */
+    public function generateSignature($url, array &$parameters)
+    {
+        $parameters['AWSAccessKeyId']   = $this->_accessKey;
+        $parameters['SignatureVersion'] = $this->_signatureVersion;
+        $parameters['Version']          = $this->_apiVersion;
+        $parameters['SignatureMethod']  = $this->_signatureMethod;
+        if(!isset($parameters['Timestamp'])) {
+            $parameters['Timestamp']    = gmdate('Y-m-d\TH:i:s\Z', time()+10);
+        }
+
+        $data = $this->_signParameters($url, $parameters);
+        
+        return $data;
+    }
+    
+    /**
+     * Set http request type to POST or GET
+     * @param $method string
+     */
+    public function setHttpMethod($method = "POST") {
+        $this->_httpMethod = strtoupper($method);
+    }
+    
+    /**
+     * Get the current http request type
+     * @return string
+     */
+    public function getHttpMethod()
+    {
+        return $this->_httpMethod;
+    }
+
+    /**
+     * Computes the RFC 2104-compliant HMAC signature for request parameters
+     *
+     * This implements the Amazon Web Services signature, as per the following
+     * specification:
+     *
+     * 1. Sort all request parameters (including <tt>SignatureVersion</tt> and
+     *    excluding <tt>Signature</tt>, the value of which is being created),
+     *    ignoring case.
+     *
+     * 2. Iterate over the sorted list and append the parameter name (in its
+     *    original case) and then its value. Do not URL-encode the parameter
+     *    values before constructing this string. Do not use any separator
+     *    characters when appending strings.
+     *
+     * @param  string $queue_url  Queue URL
+     * @param  array  $parameters the parameters for which to get the signature.
+     *
+     * @return string the signed data.
+     */
+    protected function _signParameters($url, array &$paramaters)
+    {
+        $data = $this->_httpMethod . "\n";
+        $data .= parse_url($url, PHP_URL_HOST) . "\n";
+        $data .= ('' == $path = parse_url($url, PHP_URL_PATH)) ? '/' : $path;
+        $data .= "\n";
+
+        uksort($paramaters, 'strcmp');
+        unset($paramaters['Signature']);
+
+        $arrData = array();
+        foreach($paramaters as $key => $value) {
+            $arrData[] = $key . '=' . str_replace('%7E', '~', rawurlencode($value));
+        }
+
+        $data .= implode('&', $arrData);
+
+        $hmac = Zend_Crypt_Hmac::compute($this->_secretKey, 'SHA256', $data, Zend_Crypt_Hmac::BINARY);
+
+        $paramaters['Signature'] = base64_encode($hmac);
+
+        return $data;
+    }
+}

+ 578 - 0
library/Zend/Service/Amazon/SimpleDb.php

@@ -0,0 +1,578 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Service_Amazon_Abstract
+ */
+require_once 'Zend/Service/Amazon/Abstract.php';
+
+/**
+ * @see Zend_Service_Amazon_SimpleDb_Response
+ */
+require_once 'Zend/Service/Amazon/SimpleDb/Response.php';
+
+/**
+ * @see Zend_Service_Amazon_SimpleDb_Page
+ */
+require_once 'Zend/Service/Amazon/SimpleDb/Page.php';
+
+/**
+ * @see Zend_Service_Amazon_SimpleDb_Attribute
+ */
+require_once 'Zend/Service/Amazon/SimpleDb/Attribute.php';
+
+/**
+ * @see Zend_Service_Amazon_SimpleDb_Exception
+ */
+require_once 'Zend/Service/Amazon/SimpleDb/Exception.php';
+
+/**
+ * @see Zend_Crypt_Hmac
+ */
+require_once 'Zend/Crypt/Hmac.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_SimpleDb extends Zend_Service_Amazon_Abstract
+{
+    /* Notes */
+    // TODO SSL is required
+
+    /**
+     * The HTTP query server
+     */
+    protected $_sdbEndpoint = 'sdb.amazonaws.com';
+
+    /**
+     * Period after which HTTP request will timeout in seconds
+     */
+    protected $_httpTimeout = 10;
+
+    /**
+     * The API version to use
+     */
+    protected $_sdbApiVersion = '2009-04-15';
+
+    /**
+     * Signature Version
+     */
+    protected $_signatureVersion = '2';
+
+    /**
+     * Signature Encoding Method
+     */
+    protected $_signatureMethod = 'HmacSHA256';
+
+    /**
+     * Create Amazon SimpleDB client.
+     *
+     * @param  string $access_key       Override the default Access Key
+     * @param  string $secret_key       Override the default Secret Key
+     * @param  string $region           Sets the AWS Region
+     * @return void
+     */
+    public function __construct($accessKey, $secretKey)
+    {
+        parent::__construct($accessKey, $secretKey);
+        $this->setEndpoint("https://" . $this->_sdbEndpoint);
+    }
+
+	/**
+     * Set SimpleDB endpoint to use
+     *
+     * @param string|Zend_Uri_Http $endpoint
+     * @return Zend_Service_Amazon_SimpleDb
+     */
+    public function setEndpoint($endpoint)
+    {
+    	if(!($endpoint instanceof Zend_Uri_Http)) {
+    		$endpoint = Zend_Uri::factory($endpoint);
+    	}
+    	if(!$endpoint->valid()) {
+    		require_once 'Zend/Service/Amazon/SimpleDb/Exception.php';
+    		throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid endpoint supplied");
+    	}
+    	$this->_endpoint = $endpoint;
+    	return $this;
+    }
+
+    /**
+     * Get SimpleDB endpoint
+     *
+     * @return Zend_Uri_Http
+     */
+    public function getEndpoint() 
+    {
+    	return $this->_endpoint;
+    }
+
+    /**
+     * Get attributes API method
+     *
+     * @param string $domainName Domain name within database
+     * @param string 
+     */
+    public function getAttributes(
+        $domainName, $itemName, $attributeName = null
+    ) {
+        $params               = array();
+	    $params['Action']     = 'GetAttributes';
+	    $params['DomainName'] = $domainName;
+	    $params['ItemName']   = $itemName;
+
+	    if (isset($attributeName)) {
+	        $params['AttributeName'] = $attributeName;
+	    }
+
+	    $response = $this->_sendRequest($params);
+        $document = $response->getSimpleXMLDocument();
+
+        $attributeNodes = $document->GetAttributesResult->Attribute;
+
+        // Return an array of arrays
+        $attributes = array();
+        foreach($attributeNodes as $attributeNode) {
+            $name       = (string)$attributeNode->Name;
+            $valueNodes = $attributeNode->Value;
+            $data       = null;
+            if (is_array($valueNodes) && !empty($valueNodes)) {
+                $data = array();
+                foreach($valueNodes as $valueNode) {
+                    $data[] = (string)$valueNode;
+                }
+            } elseif (isset($valueNodes)) {
+                $data = (string)$valueNodes;
+            }
+            if (isset($attributes[$name])) {
+                $attributes[$name]->addValue($data);    
+            } else {
+                $attributes[$name] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $name, $data);
+            }
+        }
+        return $attributes;
+    }
+
+    /**
+     * Push attributes
+     *
+     * @param  string $domainName
+     * @param  string $itemName
+     * @param  array|Traverable $attributes
+     * @param  array $replace
+     * @return void
+     */
+    public function putAttributes(
+        $domainName, $itemName, $attributes, $replace = array()
+    ) {
+        $params               = array();
+	    $params['Action']     = 'PutAttributes';
+	    $params['DomainName'] = $domainName;
+	    $params['ItemName']   = $itemName;
+
+	    $index = 0;
+	    foreach ($attributes as $attribute) {
+	        $attributeName = $attribute->getName();
+            foreach ($attribute->getValues() as $value) {
+	            $params['Attribute.' . $index . '.Name']  = $attributeName;
+                $params['Attribute.' . $index . '.Value'] = $value;
+
+	            // Check if it should be replaced
+                if(array_key_exists($attributeName, $replace) && $replace[$attributeName]) {
+                    $params['Attribute.' . $index . '.Replace'] = 'true';
+                }
+                $index++;
+            }
+	    }
+
+	    // Exception should get thrown if there's an error
+        $response = $this->_sendRequest($params);
+    }
+
+    /**
+     * Add many attributes at once
+     * 
+     * @param  array $items 
+     * @param  string $domainName 
+     * @param  array $replace 
+     * @return void
+     */
+    public function batchPutAttributes($items, $domainName, array $replace = array()) 
+    {
+
+        $params               = array();
+        $params['Action']     = 'BatchPutAttributes';
+        $params['DomainName'] = $domainName;
+
+        $itemIndex = 0;
+        foreach ($items as $name => $attributes) {
+            $params['Item.' . $itemIndex . '.ItemName'] = $name;
+            $attributeIndex = 0;
+            foreach ($attributes as $attribute) {
+                $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Name'] = $attribute->getName();
+                if (isset($replace[$itemIndex]) 
+                    && isset($replace[$itemIndex][$attributeIndex]) 
+                    && $replace[$itemIndex][$attributeIndex]
+                ) {
+                    $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Replace'] = 'true';
+                }
+                foreach($attribute->getValues() as $value) {
+                    $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Value'] = $value;
+                }
+                $attributeIndex++;
+            }
+            $itemIndex++;
+        }
+
+        $response = $this->_sendRequest($params);
+    }
+
+    /**
+     * Delete attributes
+     * 
+     * @param  string $domainName 
+     * @param  string $itemName 
+     * @param  array $attributes 
+     * @return void
+     */
+    public function deleteAttributes($domainName, $itemName, array $attributes = array()) 
+    {
+        $params               = array();
+	    $params['Action']     = 'DeleteAttributes';
+	    $params['DomainName'] = $domainName;
+	    $params['ItemName']   = $itemName;
+
+	    $attributeIndex = 0;
+	    foreach ($attributes as $attribute) {
+	        foreach ($attribute->getValues() as $value) {
+	            $params['Attribute.' . $attributeIndex . '.Name'] = $attribute->getName();
+	            $params['Attribute.' . $attributeIndex . '.Value'] = $value;
+                $attributeIndex++;
+	        }
+	    }
+
+        $response = $this->_sendRequest($params);
+
+        return true;
+    }
+
+    /**
+     * List domains
+     *
+     * @param $maxNumberOfDomains int
+     * @param $nextToken          int
+     * @return array              0 or more domain names
+     */
+    public function listDomains($maxNumberOfDomains = 100, $nextToken = null) 
+    {
+        $params                       = array();
+	    $params['Action']             = 'ListDomains';
+	    $params['MaxNumberOfDomains'] = $maxNumberOfDomains;
+
+	    if (null !== $nextToken) {
+	        $params['NextToken'] = $nextToken;
+	    }
+        $response = $this->_sendRequest($params);
+
+        $domainNodes = $response->getSimpleXMLDocument()->ListDomainsResult->DomainName;
+
+        $data = array();
+        foreach ($domainNodes as $domain) {
+            $data[] = (string)$domain;
+        }
+
+        $nextTokenNode = $response->getSimpleXMLDocument()->ListDomainsResult->NextToken;
+        $nextToken     = (string)$nextTokenNode;
+        $nextToken     = ''?null:$nextToken;
+
+        return new Zend_Service_Amazon_SimpleDb_Page($data, $nextToken);
+    }
+
+    /**
+     * Retrieve domain metadata
+     *
+     * @param $domainName string Name of the domain for which metadata will be requested
+     * @return array Key/value array of metadatum names and values.
+     */
+    public function domainMetadata($domainName) 
+    {
+        $params               = array();
+	    $params['Action']     = 'DomainMetadata';
+	    $params['DomainName'] = $domainName;
+        $response             = $this->_sendRequest($params);
+
+        $document = $response->getSimpleXMLDocument();
+
+        $metadataNodes = $document->DomainMetadataResult->children();
+        $metadata      = array();
+        foreach ($metadataNodes as $metadataNode) {
+            $name            = $metadataNode->getName();
+            $metadata[$name] = (string)$metadataNode;
+        }
+
+        return $metadata;
+    }
+
+    /**
+     * Create a new domain
+     *
+     * @param $domainName	string	Valid domain name of the domain to create
+     * @return 				boolean True if successful, false if not
+     */
+	public function createDomain($domainName) 
+	{
+        $params               = array();
+	    $params['Action']     = 'CreateDomain';
+	    $params['DomainName'] = $domainName;
+        $response             = $this->_sendRequest($params);
+        return $response->getHttpResponse()->isSuccessful();
+    }
+
+    /**
+     * Delete a domain
+     *
+     * @param 	$domainName string  Valid domain name of the domain to delete
+     * @return 				boolean True if successful, false if not
+     */
+	public function deleteDomain($domainName) 
+	{
+	    $params               = array();
+	    $params['Action']     = 'DeleteDomain';
+	    $params['DomainName'] = $domainName;
+        $response             = $this->_sendRequest($params);
+        return $response->getHttpResponse()->isSuccessful();
+    }
+
+    /**
+     * Select items from the database
+     *
+     * @param  string $selectExpression
+     * @param  null|string $nextToken
+     * @return Zend_Service_Amazon_SimpleDb_Page
+     */
+	public function select($selectExpression, $nextToken = null) 
+	{
+        $params                     = array();
+	    $params['Action']           = 'Select';
+	    $params['SelectExpression'] = $selectExpression;
+
+	    if (null !== $nextToken) {
+	        $params['NextToken'] = $nextToken;
+	    }
+
+        $response = $this->_sendRequest($params);
+        $xml      = $response->getSimpleXMLDocument();
+
+        $attributes = array();
+        foreach ($xml->SelectResult->Item as $item) {
+            $itemName = (string)$item->Name;
+
+            foreach ($item->Attribute as $attribute) {
+                $attributeName = (string)$attribute->Name;
+
+                $values = array();
+                foreach ($attribute->Value as $value) {
+                    $values[] = (string)$value;
+                }
+                $attributes[$itemName][$attributeName] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $attributeName, $values);
+            }
+        }
+
+        $nextToken = (string)$xml->NextToken;
+
+        return new Zend_Service_Amazon_SimpleDb_Page($attributes, $nextToken);
+    }
+    
+	/**
+	 * Quote SDB value
+	 * 
+	 * Wraps it in ''
+	 * 
+	 * @param string $value
+	 * @return string
+	 */
+    public function quote($value)
+    {
+    	// wrap in single quotes and convert each ' inside to ''
+    	return "'" . str_replace("'", "''", $value) . "'";
+    }
+    
+	/**
+	 * Quote SDB column or table name
+	 * 
+	 * Wraps it in ``
+	 * @param string $name
+	 * @return string
+	 */
+    public function quoteName($name)
+    {
+    	if (preg_match('/^[a-z_$][a-z0-9_$-]*$/i', $name) == false) {
+    		throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid name: can contain only alphanumeric characters, \$ and _");
+    	}
+    	return "`$name`";
+    }
+    
+   /**
+     * Sends a HTTP request to the SimpleDB service using Zend_Http_Client
+     *
+     * @param array $params         List of parameters to send with the request
+     * @return Zend_Service_Amazon_SimpleDb_Response
+     * @throws Zend_Service_Amazon_SimpleDb_Exception
+     */
+    protected function _sendRequest(array $params = array())
+    {
+        // UTF-8 encode all parameters and replace '+' characters
+        foreach ($params as $name => $value) {
+            unset($params[$name]);
+            $params[utf8_encode($name)] = $value;
+        }
+
+        $params = $this->_addRequiredParameters($params);
+
+        try {
+            /* @var $request Zend_Http_Client */
+            $request = self::getHttpClient();
+            $request->resetParameters();
+
+            $request->setConfig(array(
+                'timeout' => $this->_httpTimeout
+            ));
+
+
+            $request->setUri($this->getEndpoint());
+            $request->setMethod(Zend_Http_Client::POST);
+            foreach ($params as $key => $value) {
+                $params_out[] = rawurlencode($key)."=".rawurlencode($value);
+            }
+            $request->setRawData(join('&', $params_out), Zend_Http_Client::ENC_URLENCODED);
+            $httpResponse = $request->request();
+        } catch (Zend_Http_Client_Exception $zhce) {
+            $message = 'Error in request to AWS service: ' . $zhce->getMessage();
+            throw new Zend_Service_Amazon_SimpleDb_Exception($message, $zhce->getCode());
+        }
+        $response = new Zend_Service_Amazon_SimpleDb_Response($httpResponse);
+        $this->_checkForErrors($response);
+        return $response;
+    }
+
+    /**
+     * Adds required authentication and version parameters to an array of
+     * parameters
+     *
+     * The required parameters are:
+     * - AWSAccessKey
+     * - SignatureVersion
+     * - Timestamp
+     * - Version and
+     * - Signature
+     *
+     * If a required parameter is already set in the <tt>$parameters</tt> array,
+     * it is overwritten.
+     *
+     * @param array $parameters the array to which to add the required
+     *                          parameters.
+     *
+     * @return array
+     */
+    protected function _addRequiredParameters(array $parameters)
+    {
+        $parameters['AWSAccessKeyId']   = $this->_getAccessKey();
+        $parameters['SignatureVersion'] = $this->_signatureVersion;
+        $parameters['Timestamp']        = gmdate('c');
+        $parameters['Version']          = $this->_sdbApiVersion;
+        $parameters['SignatureMethod']  = $this->_signatureMethod;
+        $parameters['Signature']        = $this->_signParameters($parameters);
+
+        return $parameters;
+    }
+
+    /**
+     * Computes the RFC 2104-compliant HMAC signature for request parameters
+     *
+     * This implements the Amazon Web Services signature, as per the following
+     * specification:
+     *
+     * 1. Sort all request parameters (including <tt>SignatureVersion</tt> and
+     *    excluding <tt>Signature</tt>, the value of which is being created),
+     *    ignoring case.
+     *
+     * 2. Iterate over the sorted list and append the parameter name (in its
+     *    original case) and then its value. Do not URL-encode the parameter
+     *    values before constructing this string. Do not use any separator
+     *    characters when appending strings.
+     *
+     * @param array  $parameters the parameters for which to get the signature.
+     * @param string $secretKey  the secret key to use to sign the parameters.
+     *
+     * @return string the signed data.
+     */
+    protected function _signParameters(array $paramaters)
+    {
+        $data  = "POST\n";
+        $data .= $this->getEndpoint()->getHost() . "\n";
+        $data .= "/\n";
+
+        uksort($paramaters, 'strcmp');
+        unset($paramaters['Signature']);
+
+        $arrData = array();
+        foreach ($paramaters as $key => $value) {
+            $value = urlencode($value);
+            $value = str_replace("%7E", "~", $value);
+            $value = str_replace("+", "%20", $value);
+            $arrData[] = urlencode($key) . '=' . $value;
+        }
+
+        $data .= implode('&', $arrData);
+
+        require_once 'Zend/Crypt/Hmac.php';
+        $hmac = Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'SHA256', $data, Zend_Crypt_Hmac::BINARY);
+
+        return base64_encode($hmac);
+    }
+
+    /**
+     * Checks for errors responses from Amazon
+     *
+     * @param Zend_Service_Amazon_SimpleDb_Response $response the response object to
+     *                                                   check.
+     *
+     * @return void
+     *
+     * @throws Zend_Service_Amazon_SimpleDb_Exception if one or more errors are
+     *         returned from Amazon.
+     */
+    private function _checkForErrors(Zend_Service_Amazon_SimpleDb_Response $response)
+    {
+        $xpath = new DOMXPath($response->getDocument());
+        $list  = $xpath->query('//Error');
+        if ($list->length > 0) {
+            $node    = $list->item(0);
+            $code    = $xpath->evaluate('string(Code/text())', $node);
+            $message = $xpath->evaluate('string(Message/text())', $node);
+            throw new Zend_Service_Amazon_SimpleDb_Exception($message, 0, $code);
+        }
+    }
+}

+ 100 - 0
library/Zend/Service/Amazon/SimpleDb/Attribute.php

@@ -0,0 +1,100 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: Response.php 17539 2009-08-10 22:51:26Z mikaelkael $
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_SimpleDb_Attribute
+{
+    protected $_itemName;
+    protected $_name;
+    protected $_values;
+
+    /**
+     * Constructor
+     * 
+     * @param  string $itemName 
+     * @param  string $name 
+     * @param  array $values 
+     * @return void
+     */
+    function __construct($itemName, $name, $values) 
+    {
+        $this->_itemName = $itemName;
+        $this->_name     = $name;
+
+        if (!is_array($values)) {
+            $this->_values = array($values);
+        } else {
+            $this->_values = $values;
+        }
+    }
+
+	/**
+     * Return the item name to which the attribute belongs
+     *
+     * @return string
+     */
+    public function getItemName ()
+    {
+        return $this->_itemName;
+    }
+
+	/**
+     * Retrieve attribute values
+     *
+     * @return array
+     */
+    public function getValues()
+    {
+        return $this->_values;
+    }
+
+	/**
+     * Retrieve the attribute name
+     *
+     * @return string
+     */
+    public function getName ()
+    {
+        return $this->_name;
+    }
+    
+    /**
+     * Add value
+     * 
+     * @param  mixed $value 
+     * @return void
+     */
+    public function addValue($value)
+    {
+        if (is_array($value)) {
+             $this->_values += $value;   
+        } else {
+            $this->_values[] = $value;
+        }
+    }
+}

+ 66 - 0
library/Zend/Service/Amazon/SimpleDb/Exception.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Service_Amazon_Exception
+ */
+require_once 'Zend/Service/Amazon/Exception.php';
+
+/**
+ * The Custom Exception class that allows you to have access to the AWS Error Code.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_SimpleDb_Exception extends Zend_Service_Amazon_Exception
+{
+    /**
+     * @var string
+     */
+    private $_awsErrorCode = '';
+
+    /**
+     * Constructor
+     * 
+     * @param string $message 
+     * @param int $code 
+     * @param string $awsErrorCode 
+     * @return void
+     */
+    public function __construct($message, $code = 0, $awsErrorCode = '')
+    {
+        parent::__construct($message, $code);
+        $this->_awsErrorCode = $awsErrorCode;
+    }
+
+    /**
+     * Get AWS error code
+     * 
+     * @return string
+     */
+    public function getErrorCode()
+    {
+        return $this->_awsErrorCode;
+    }
+}

+ 97 - 0
library/Zend/Service/Amazon/SimpleDb/Page.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Service_Amazon_Exception
+ */
+require_once 'Zend/Service/Amazon/Exception.php';
+
+/**
+ * The Custom Exception class that allows you to have access to the AWS Error Code.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_SimpleDb_Page
+{
+    /** @var string Page data */
+    protected $_data;
+
+    /** @var string|null Token identifying page */
+    protected $_token;
+
+    /**
+     * Constructor
+     * 
+     * @param  string $data 
+     * @param  string|null $token 
+     * @return void
+     */
+    public function __construct($data, $token = null) 
+    {
+        $this->_data  = $data;
+        $this->_token = $token;
+    }
+
+    /**
+     * Retrieve page data
+     * 
+     * @return string
+     */
+    public function getData() 
+    {
+        return $this->_data;
+    }
+
+    /**
+     * Retrieve token
+     * 
+     * @return string|null
+     */
+    public function getToken() 
+    {
+        return $this->_token;
+    }
+
+    /**
+     * Determine whether this is the last page of data
+     * 
+     * @return void
+     */
+    public function isLast() 
+    {
+        return (null === $this->_token);
+    }
+
+    /**
+     * Cast to string
+     * 
+     * @return string
+     */
+    public function __toString() 
+    {
+        return "Page with token: " . $this->_token 
+             . "\n and data: " . $this->_data;
+    }
+}

+ 190 - 0
library/Zend/Service/Amazon/SimpleDb/Response.php

@@ -0,0 +1,190 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Http_Response
+ */
+require_once 'Zend/Http/Response.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage SimpleDb
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Service_Amazon_SimpleDb_Response 
+{
+    /**
+     * XML namespace used for SimpleDB responses.
+     */
+    protected $_xmlNamespace = 'http://sdb.amazonaws.com/doc/2009-04-15/';
+
+    /**
+     * The original HTTP response
+     *
+     * This contains the response body and headers.
+     *
+     * @var Zend_Http_Response
+     */
+    private $_httpResponse = null;
+
+    /**
+     * The response document object
+     *
+     * @var DOMDocument
+     */
+    private $_document = null;
+
+    /**
+     * The response XPath
+     *
+     * @var DOMXPath
+     */
+    private $_xpath = null;
+
+    /**
+     * Last error code
+     *
+     * @var integer
+     */
+    private $_errorCode = 0;
+
+    /**
+     * Last error message
+     *
+     * @var string
+     */
+    private $_errorMessage = '';
+
+    /**
+     * Creates a new high-level SimpleDB response object
+     *
+     * @param  Zend_Http_Response $httpResponse the HTTP response.
+     * @return void
+     */
+    public function __construct(Zend_Http_Response $httpResponse)
+    {
+        $this->_httpResponse = $httpResponse;
+    }
+
+    /**
+     * Gets the XPath object for this response
+     *
+     * @return DOMXPath the XPath object for response.
+     */
+    public function getXPath()
+    {
+        if ($this->_xpath === null) {
+            $document = $this->getDocument();
+            if ($document === false) {
+                $this->_xpath = false;
+            } else {
+                $this->_xpath = new DOMXPath($document);
+                $this->_xpath->registerNamespace('sdb',
+                    $this->getNamespace());
+            }
+        }
+
+        return $this->_xpath;
+    }
+
+    /**
+     * Gets the SimpleXML document object for this response
+     *
+     * @return SimpleXMLElement
+     */
+    public function getSimpleXMLDocument()
+    {
+        try {
+            $body = $this->_httpResponse->getBody();
+        } catch (Zend_Http_Exception $e) {
+            $body = false;
+        }
+
+       
+        return simplexml_load_string($body);
+    }
+    
+    /**
+     * Get HTTP response object
+     * 
+     * @return Zend_Http_Response
+     */
+    public function getHttpResponse() 
+    {
+        return $this->_httpResponse;
+    }
+    
+    /**
+     * Gets the document object for this response
+     *
+     * @return DOMDocument the DOM Document for this response.
+     */
+    public function getDocument()
+    {
+        try {
+            $body = $this->_httpResponse->getBody();
+        } catch (Zend_Http_Exception $e) {
+            $body = false;
+        }
+
+        if ($this->_document === null) {
+            if ($body !== false) {
+                // turn off libxml error handling
+                $errors = libxml_use_internal_errors();
+
+                $this->_document = new DOMDocument();
+                if (!$this->_document->loadXML($body)) {
+                    $this->_document = false;
+                }
+                
+                // reset libxml error handling
+                libxml_clear_errors();
+                libxml_use_internal_errors($errors);
+            } else {
+                $this->_document = false;
+            }
+        }
+
+        return $this->_document;
+    }
+
+    /**
+     * Return the current set XML Namespace.
+     *
+     * @return string
+     */
+    public function getNamespace()
+    {
+        return $this->_xmlNamespace;
+    }
+
+    /**
+     * Set a new XML Namespace
+     *
+     * @param string $namespace
+     */
+    public function setNamespace($namespace)
+    {
+        $this->_xmlNamespace = $namespace;
+    }
+}

+ 28 - 1
tests/TestConfiguration.php.dist

@@ -1,5 +1,4 @@
 <?php
-
 /**
  * Zend Framework
  *
@@ -75,6 +74,16 @@ define('TESTS_ZEND_CACHE_MEMCACHED_PORT', 11211);
 define('TESTS_ZEND_CACHE_MEMCACHED_PERSISTENT', true);
 
 /**
+ * Zend_Cloud online tests
+ *
+ * You may need to provide connection details for specific adapters under their 
+ * specific configuration settings elsewhere in this file.
+ */
+define('TESTS_ZEND_CLOUD_STORAGE_NIRVANIX_DIRECTORY',     'simplecloud');
+define('TESTS_ZEND_CLOUD_STORAGE_WINDOWSAZURE_CONTAINER', 'simplecloudcontainer');
+
+
+/**
  * Zend_Controller
  *
  * TESTS_ZEND_CONTROLLER_DISPATCHER_OB => test disabling output buffering in
@@ -596,6 +605,14 @@ define('TESTS_ZEND_SERVICE_LIVEDOCX_USERNAME', false);
 define('TESTS_ZEND_SERVICE_LIVEDOCX_PASSWORD', false);
 
 /**
+ * Zend_Service_Nirvanix online tests 
+ */
+define('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_ENABLED', false);
+define('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_ACCESSKEY', false);
+define('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_PASSWORD', false);
+define('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_USERNAME', false);
+
+/**
  * Zend_Service_ReCaptcha tests
  */
 define('TESTS_ZEND_SERVICE_RECAPTCHA_ENABLED', false);
@@ -711,6 +728,16 @@ define('TESTS_ZEND_SERVICE_WINDOWSAZURE_DIAGNOSTICS_RUNONPROD',           false)
 define('TESTS_ZEND_SERVICE_WINDOWSAZURE_DIAGNOSTICS_CONTAINER_PREFIX',    'phpazuretestdiag');
 
 /**
+ * Zend_Cloud related configuration
+ */
+define('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ENABLED',     true);
+define('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTNAME', 'provide account here');
+define('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTKEY',  'provide key here');
+define('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_TABLE_HOST',  'table.core.windows.net');
+define('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_QUEUE_HOST',  'queue.core.windows.net');
+define('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_HOST','blob.core.windows.net');
+
+/**
  * Zend_Service_Yahoo online tests
  */
 define('TESTS_ZEND_SERVICE_YAHOO_ONLINE_ENABLED', false);

+ 2 - 0
tests/Zend/AllTests.php

@@ -35,6 +35,7 @@ require_once 'Zend/Auth/AllTests.php';
 require_once 'Zend/Barcode/AllTests.php';
 require_once 'Zend/Cache/AllTests.php';
 require_once 'Zend/Captcha/AllTests.php';
+require_once 'Zend/Cloud/AllTests.php';
 require_once 'Zend/CodeGenerator/Php/AllTests.php';
 require_once 'Zend/Db/AllTests.php';
 require_once 'Zend/Dom/AllTests.php';
@@ -171,6 +172,7 @@ class Zend_AllTests
         $suite->addTest(Zend_Barcode_AllTests::suite());
         $suite->addTest(Zend_Cache_AllTests::suite());
         $suite->addTest(Zend_Captcha_AllTests::suite());
+        $suite->addTest(Zend_Cloud_AllTests::suite());
         $suite->addTest(Zend_CodeGenerator_Php_AllTests::suite());
         $suite->addTestSuite('Zend_ConfigTest');
         $suite->addTest(Zend_Config_AllTests::suite());

+ 72 - 0
tests/Zend/Cloud/AllTests.php

@@ -0,0 +1,72 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Cloud_AllTests::main');
+}
+
+require_once dirname(__FILE__) . '/../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_DocumentService_AllTests
+ */
+require_once 'Zend/Cloud/DocumentService/AllTests.php';
+
+/**
+ * @see Zend_Cloud_QueueService_AllTests
+ */
+require_once 'Zend/Cloud/QueueService/AllTests.php';
+
+/**
+ * @see Zend_Cloud_StorageService_AllTests
+ */
+require_once 'Zend/Cloud/StorageService/AllTests.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Cloud
+ */
+class Zend_Cloud_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Cloud');
+
+        $suite->addTest(Zend_Cloud_DocumentService_AllTests::suite());
+        $suite->addTest(Zend_Cloud_QueueService_AllTests::suite());
+        $suite->addTest(Zend_Cloud_StorageService_AllTests::suite());
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_AllTests::main') {
+    Zend_Cloud_AllTests::main();
+}

+ 79 - 0
tests/Zend/Cloud/DocumentService/Adapter/AllTests.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_DocumentService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Cloud_DocumentService_Adapter_AllTests::main');
+}
+
+/**
+ * Test helper
+ */
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_DocumentService_Adapter_SimpleDbTest
+ */
+require_once 'Zend/Cloud/DocumentService/Adapter/SimpleDbTest.php';
+
+/**
+ * @see Zend_Cloud_DocumentService_Adapter_WindowsAzureTest
+ */
+require_once 'Zend/Cloud/DocumentService/Adapter/WindowsAzureTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud_DocumentService_Adapter
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Adapter_AllTests
+{
+    /**
+     * Runs this test suite
+     *
+     * @return void
+     */
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    /**
+     * Creates and returns this test suite
+     *
+     * @return PHPUnit_Framework_TestSuite
+     */
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Cloud');
+
+        $suite->addTestSuite('Zend_Cloud_DocumentService_Adapter_SimpleDbTest');
+        $suite->addTestSuite('Zend_Cloud_DocumentService_Adapter_WindowsAzureTest');
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_DocumentService_Adapter_AllTests::main') {
+    Zend_Cloud_DocumentService_Adapter_AllTests::main();
+}

+ 198 - 0
tests/Zend/Cloud/DocumentService/Adapter/SimpleDbTest.php

@@ -0,0 +1,198 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_DocumentService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_DocumentService_Adapter_SimpleDbTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_DocumentService_Adapter_SimpleDbTest::main");
+}
+
+
+/**
+ * Test helper 
+ */
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_DocumentServiceTestCase
+ */
+require_once 'Zend/Cloud/DocumentService/TestCase.php';
+
+/** @see Zend_Cloud_DocumenteService_Factory */
+require_once 'Zend/Cloud/DocumentService/Factory.php';
+
+/** @see Zend_Cloud_DocumenteService_Adapter_SimpleDb */
+require_once 'Zend/Cloud/DocumentService/Adapter/SimpleDb.php';
+
+/** @see Zend_Config */
+require_once 'Zend/Config.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Adapter_SimpleDbTest 
+    extends Zend_Cloud_DocumentService_TestCase
+{
+    /**
+     * Period to wait for propagation in seconds
+     * Should be set by adapter
+     *
+     * @var int
+     */
+    protected $_waitPeriod = 10;
+    
+    protected $_clientType = 'Zend_Service_Amazon_SimpleDb';
+    
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+    
+    public function testUpdateDocumentMergeAll() 
+    {
+        $data = $this->_getDocumentData();
+        $name = $this->_collectionName("testMerge");
+        $this->_commonDocument->createCollection($name);
+
+        $doc = $this->_makeDocument($data[0]);
+        $this->_commonDocument->insertDocument($name, $doc);
+        $doc1 = $this->_makeDocument($data[1]);
+        $this->_wait();
+        $this->_commonDocument->updateDocument($name, $doc->getID(), $doc1, 
+            array(Zend_Cloud_DocumentService_Adapter_SimpleDb::MERGE_OPTION => true));
+        $this->_wait();
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc->getID());
+        $this->assertTrue($fetchdoc instanceof Zend_Cloud_DocumentService_Document, "New document not found");
+        $this->assertContains($doc->name, $fetchdoc->name, "Name field did not update: " . var_export($fetchdoc->getFields(), 1));
+        $this->assertContains($doc1->name, $fetchdoc->name, "Name field did not update: " . var_export($fetchdoc->getFields(), 1));
+        $this->assertContains((string) $doc->year, $fetchdoc->year, "Year field did not update: " . var_export($fetchdoc->getFields(), 1));
+        $this->assertContains((string) $doc1->year, $fetchdoc->year, "Year field did not update: " . var_export($fetchdoc->getFields(), 1));
+        
+        $this->_commonDocument->deleteCollection($name);
+    }
+    
+    public function testUpdateDocumentMergeSome() 
+    {
+        $data = $this->_getDocumentData();
+        $name = $this->_collectionName("testMerge");
+        $this->_commonDocument->createCollection($name);
+
+        $doc = $this->_makeDocument($data[0]);
+        $this->_commonDocument->insertDocument($name, $doc);
+        $doc1 = $this->_makeDocument($data[1]);
+        $this->_wait();
+        $this->_commonDocument->updateDocument($name, $doc->getID(), $doc1, 
+            array(Zend_Cloud_DocumentService_Adapter_SimpleDb::MERGE_OPTION => 
+                array("year" => true, "pages" => true)));
+        $this->_wait();
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc->getID());
+        $this->assertTrue($fetchdoc instanceof Zend_Cloud_DocumentService_Document, "New document not found");
+        $this->assertEquals($doc1->name, $fetchdoc->name, "Name field did not update");
+        $this->assertContains((string) $doc1->pages, $fetchdoc->pages, "Page field did not update");
+        $this->assertContains((string) $doc->pages, $fetchdoc->pages, "Page field did not update");
+        $this->assertContains((string) $doc1->year, $fetchdoc->year, "Year field did not update");
+        $this->assertContains((string) $doc->year, $fetchdoc->year, "Year field did not update");
+        
+        $this->_commonDocument->deleteCollection($name);
+    }
+    
+    static function getConfigArray()
+    {
+        return array(
+                Zend_Cloud_DocumentService_Factory::DOCUMENT_ADAPTER_KEY => 'Zend_Cloud_DocumentService_Adapter_SimpleDb',
+                Zend_Cloud_DocumentService_Adapter_SimpleDb::AWS_ACCESS_KEY => constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID'),
+                Zend_Cloud_DocumentService_Adapter_SimpleDb::AWS_SECRET_KEY => constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY'),
+            );
+    } 
+
+    protected function _getConfig() 
+    {
+        if (!defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ENABLED') ||
+            !constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ENABLED') ||
+            !defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID') ||
+            !defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY')) {
+            $this->markTestSkipped("Amazon SimpleDB access not configured, skipping test");        
+        }        
+        
+        $config = new Zend_Config(self::getConfigArray());
+
+        return $config;
+    }
+    
+    protected function _getDocumentData()
+    {
+        return array( 
+            array(
+	        	parent::ID_FIELD => "0385333498",
+	        	"name" =>	"The Sirens of Titan",
+	        	"author" =>	"Kurt Vonnegut", 
+	        	"year"	=> 1959,
+	        	"pages" =>	336,
+	        	"keyword" => array("Book", "Paperback")
+	        	),
+            array(
+	        	parent::ID_FIELD => "0802131786",
+	        	"name" =>	"Tropic of Cancer",
+	        	"author" =>	"Henry Miller", 
+	        	"year"	=> 1934,
+	        	"pages" =>	318,
+	        	"keyword" => array("Book")
+	        	),
+            array(
+	        	parent::ID_FIELD => "B000T9886K",
+	        	"name" =>	"In Between",
+	        	"author" =>	"Paul Van Dyk", 
+	        	"year"	=> 2007,
+	        	"keyword" => array("CD", "Music")
+	        	),
+	        array(
+	        	parent::ID_FIELD => "1579124585",
+	        	"name" =>	"The Right Stuff",
+	        	"author" =>	"Tom Wolfe", 
+	        	"year"	=> 1979,
+	        	"pages" =>	304,
+	        	"keyword" => array("American", "Book", "Hardcover")
+	        	),
+        );
+    }
+    
+    protected function _queryString($domain, $s1, $s2)
+    {
+        return "select * from $domain where itemName() = '$s1' OR itemName() = '$s2'";
+    }
+    
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_DocumentService_Adapter_SimpleDbTest::main') {
+    Zend_Cloud_DocumentService_Adapter_SimpleDbTest::main();
+}

+ 164 - 0
tests/Zend/Cloud/DocumentService/Adapter/WindowsAzureTest.php

@@ -0,0 +1,164 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_Document
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+// Call Zend_Cloud_Document_Adapter_WindowsAzureTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_DocumentService_Adapter_WindowsAzureTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_DocumentServiceTestCase
+ */
+require_once 'Zend/Cloud/DocumentService/TestCase.php';
+
+/**
+ * @see Zend_Cloud_DocumentService_Adapter_WindowsAzure
+ */
+require_once 'Zend/Cloud/DocumentService/Adapter/WindowsAzure.php';
+
+/**
+ * @see Zend_Cloud_DocumentService_Factory
+ */
+require_once 'Zend/Cloud/DocumentService/Factory.php';
+
+/** @see Zend_Config */
+require_once 'Zend/Config.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_DocumentService_Adapter_WindowsAzureTest 
+    extends Zend_Cloud_DocumentService_TestCase
+{
+    /**
+     * Period to wait for propagation in seconds
+     * Should be set by adapter
+     *
+     * @var int
+     */
+    protected $_waitPeriod = 10;
+    
+    protected $_clientType = 'Zend_Service_WindowsAzure_Storage_Table';
+    
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    public function testQueryStructOrder() 
+    {
+        try {
+            parent::testQueryStructOrder();
+        } catch(Zend_Cloud_OperationNotAvailableException $e) {
+            $this->_commonDocument->deleteCollection($this->_collectionName("testStructQuery4"));
+            $this->markTestSkipped('Azure query sorting not implemented yet');
+        }
+    }
+    
+    static function getConfigArray()
+    {
+         return array(
+            Zend_Cloud_DocumentService_Factory::DOCUMENT_ADAPTER_KEY => 'Zend_Cloud_DocumentService_Adapter_WindowsAzure',
+            Zend_Cloud_DocumentService_Adapter_WindowsAzure::ACCOUNT_NAME => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTNAME'),
+            Zend_Cloud_DocumentService_Adapter_WindowsAzure::ACCOUNT_KEY => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTKEY'),
+            Zend_Cloud_DocumentService_Adapter_WindowsAzure::HOST => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_TABLE_HOST'),
+            Zend_Cloud_DocumentService_Adapter_WindowsAzure::PROXY_HOST => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_HOST'),
+            Zend_Cloud_DocumentService_Adapter_WindowsAzure::PROXY_PORT => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_PORT'),
+            Zend_Cloud_DocumentService_Adapter_WindowsAzure::PROXY_CREDENTIALS => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_CREDENTIALS'),
+        );
+    }
+    
+    protected function _getConfig() 
+    {
+        if (!defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ENABLED') ||
+            !constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ENABLED') ||
+            !defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTNAME') ||
+            !defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTKEY')) {
+            $this->markTestSkipped("Windows Azure access not configured, skipping test");        
+        }        
+        
+        $config = new Zend_Config(self::getConfigArray());
+
+        return $config;
+    }
+    
+    protected function _getDocumentData()
+    {
+        return array( 
+            array(
+	        	parent::ID_FIELD => array("Amazon", "0385333498"),
+	        	"name" =>	"The Sirens of Titan",
+	        	"author" =>	"Kurt Vonnegut", 
+	        	"year"	=> 1959,
+	        	"pages" =>	336,
+	        	"keyword" => "Book"
+	        	),
+            array(
+	        	parent::ID_FIELD => array("Amazon", "0802131786"),
+	        	"name" =>	"Tropic of Cancer",
+	        	"author" =>	"Henry Miller", 
+	        	"year"	=> 1934,
+	        	"pages" =>	318,
+	        	"keyword" => "Book"
+	        	),
+            array(
+	        	parent::ID_FIELD => array("Amazon", "B000T9886K"),
+	        	"name" =>	"In Between",
+	        	"author" =>	"Paul Van Dyk", 
+	        	"year"	=> 2007,
+	        	"keyword" => "CD"
+	        	),
+	       array(
+	        	parent::ID_FIELD => array("Amazon", "1579124585"),
+	        	"name" =>	"The Right Stuff",
+	        	"author" =>	"Tom Wolfe", 
+	        	"year"	=> 1979,
+	        	"pages" =>	304,
+	        	"keyword" => "Book"
+	        	),
+        );
+    }
+    
+    protected function _queryString($domain, $s1, $s2)
+    {
+        $k1 = $s1[1];
+        $k2 = $s2[1];
+        return "RowKey eq '$k1' or RowKey eq '$k2'";
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_DocumentService_Adapter_WindowsAzureTest::main') {
+    Zend_Cloud_DocumentService_Adapter_WindowsAzureTest::main();
+}

+ 61 - 0
tests/Zend/Cloud/DocumentService/AllTests.php

@@ -0,0 +1,61 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Cloud_DocumentService_AllTests::main');
+}
+
+require_once 'Zend/Cloud/DocumentService/Adapter/AllTests.php';
+
+/**
+ * @see Zend_Cloud_DocumentService_FactoryTest
+ */
+require_once 'Zend/Cloud/DocumentService/FactoryTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Cloud
+ */
+class Zend_Cloud_DocumentService_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Cloud_DocumentService');
+
+        $suite->addTestSuite('Zend_Cloud_DocumentService_FactoryTest');
+        $suite->addTest(Zend_Cloud_DocumentService_Adapter_AllTests::suite());
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_DocumentService_AllTests::main') {
+    Zend_Cloud_DocumentService_AllTests::main();
+}

+ 89 - 0
tests/Zend/Cloud/DocumentService/FactoryTest.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_DocumentService_FactoryTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_DocumentService_FactoryTest::main");
+}
+
+/**
+ * @see Zend_Config_Ini
+ */
+require_once 'Zend/Config/Ini.php';
+
+/**
+ * @see Zend_Cloud_FactoryTest
+ */
+require_once 'Zend/Cloud/FactoryTest.php';
+
+/**
+ * @see Zend_Cloud_DocumentService_Factory
+ */
+require_once 'Zend/Cloud/DocumentService/Factory.php';
+
+/**
+ * Test class for Zend_Cloud_DocumentService_Factory
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Cloud
+ */
+class Zend_Cloud_DocumentService_FactoryTest extends Zend_Cloud_FactoryTest
+{
+    /**
+     * Runs the test methods of this class.
+     *
+     * @return void
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    public function testGetDocumentAdapterKey()
+    {
+        $this->assertTrue(is_string(Zend_Cloud_DocumentService_Factory::DOCUMENT_ADAPTER_KEY));
+    }
+
+    public function testGetAdapterWithConfig() {
+        // SimpleDB adapter
+        $simpleDBAdapter = Zend_Cloud_DocumentService_Factory::getAdapter(
+                                    new Zend_Config(Zend_Cloud_DocumentService_Adapter_SimpleDbTest::getConfigArray())
+                                );
+
+        $this->assertEquals('Zend_Cloud_DocumentService_Adapter_SimpleDb', get_class($simpleDbAdapter));
+        // Azure adapter
+        $azureAdapter = Zend_Cloud_DocumentService_Factory::getAdapter(
+                                    new Zend_Config(Zend_Cloud_DocumentService_Adapter_WindowsAzureTest::getConfigArray())
+                                );
+
+        $this->assertEquals('Zend_Cloud_DocumentService_Adapter_WindowsAzure', get_class($azureAdapter));
+    }
+}
+
+// Call Zend_Cloud_DocumentService_FactoryTest::main() if this source file is executed directly.
+if (PHPUnit_MAIN_METHOD == "Zend_Cloud_DocumentService_FactoryTest::main") {
+    Zend_Cloud_DocumentService_FactoryTest::main();
+}

+ 395 - 0
tests/Zend/Cloud/DocumentService/TestCase.php

@@ -0,0 +1,395 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage DocumentService
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_DocumentService_Adapter
+ */
+require_once 'Zend/Cloud/DocumentService/Adapter.php';
+
+/**
+ * @see Zend_Cloud_DocumenteService_Document
+ */
+require_once 'Zend/Cloud/DocumentService/Document.php';
+
+
+/**
+ * This class forces the adapter tests to implement tests for all methods on
+ * Zend_Cloud_DocumentService.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Cloud_DocumentService_TestCase extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Reference to Document adapter to test
+     *
+     * @var Zend_Cloud_DocumentService
+     */
+    protected $_commonDocument;
+
+    protected $_dummyCollectionNamePrefix = 'TestCollection';
+
+    protected $_dummyDataPrefix = 'TestData';
+    
+    protected $_clientType = 'stdClass';
+
+    const ID_FIELD = "__id";
+    
+    /**
+     * Config object
+     *
+     * @var Zend_Config
+     */
+
+    protected $_config;
+
+    /**
+     * Period to wait for propagation in seconds
+     * Should be set by adapter
+     *
+     * @var int
+     */
+    protected $_waitPeriod = 1;
+
+    public function testDocumentService()
+    {
+        $this->assertTrue($this->_commonDocument instanceof Zend_Cloud_DocumentService_Adapter); 
+    } 
+
+    public function testGetClient()
+    {
+    	$this->assertTrue(is_a($this->_commonDocument->getClient(), $this->_clientType)); 
+    }
+    
+    public function testCreateCollection() 
+    {
+        $name = $this->_collectionName("testCreate");
+        $this->_commonDocument->deleteCollection($name);
+        $this->_wait();
+        
+        $this->_commonDocument->createCollection($name);
+        $this->_wait();
+        
+        $collections = $this->_commonDocument->listCollections();
+        $this->assertContains($name, $collections, "New collection not in the list");
+        $this->_wait();
+        
+        $this->_commonDocument->deleteCollection($name);
+    }
+
+    public function testDeleteCollection() 
+    {
+        $name = $this->_collectionName("testDC");
+        $this->_commonDocument->createCollection($name);
+        $this->_wait();
+        
+        $collections = $this->_commonDocument->listCollections();
+        $this->assertContains($name, $collections, "New collection not in the list");
+        $this->_wait();
+
+        $this->_commonDocument->deleteCollection($name);
+        $this->_wait();
+        $this->_wait();
+
+        $collections = $this->_commonDocument->listCollections();
+        $this->assertNotContains($name, $collections, "New collection not in the list");
+    }
+
+    public function testListCollections() 
+    {
+        $this->_commonDocument->createCollection($this->_collectionName("test3"));
+        $this->_commonDocument->createCollection($this->_collectionName("test4"));
+        $this->_wait();
+        
+        $collections = $this->_commonDocument->listCollections();
+        $this->assertContains($this->_collectionName("test3"), $collections, "New collection test3 not in the list");
+        $this->assertContains($this->_collectionName("test4"), $collections, "New collection test4 not in the list");
+        $this->_wait();
+
+        $this->_commonDocument->deleteCollection($this->_collectionName("test3"));
+        $this->_commonDocument->deleteCollection($this->_collectionName("test4"));
+    }
+
+    public function testInsertDocument() 
+    {
+        $data = $this->_getDocumentData();
+        $name = $this->_collectionName("testID");
+        $this->_commonDocument->createCollection($name);
+        
+        $doc = $this->_makeDocument($data[0]);
+        $this->_commonDocument->insertDocument($name, $doc);
+        $this->_wait();
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc->getId());
+        $this->assertTrue($fetchdoc instanceof Zend_Cloud_DocumentService_Document, "New document not found");
+
+        $this->assertEquals($doc->name, $fetchdoc->name, "Name field wrong");
+        $this->assertEquals($doc->keyword, $fetchdoc->keyword, "Keyword field wrong");
+        
+        $this->_commonDocument->deleteCollection($name);
+    }
+
+    public function testDeleteDocument()
+    {
+        $data = $this->_getDocumentData();
+        $name = $this->_collectionName("testDel");
+        $this->_commonDocument->createCollection($name);
+        
+        $doc1 = $this->_makeDocument($data[0]);
+        $this->_commonDocument->insertDocument($name, $doc1);
+        $this->_wait();
+        
+        $doc2 = $this->_makeDocument($data[1]);
+        $this->_commonDocument->insertDocument($name, $doc2);
+        $this->_wait();
+        
+        $this->_commonDocument->deleteDocument($name, $doc1->getId());
+        $this->_wait();
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc1->getId());
+        $this->assertFalse($fetchdoc, "Delete failed");
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc2->getId());
+        $this->assertTrue($fetchdoc instanceof Zend_Cloud_DocumentService_Document, "New document not found");
+        $this->assertEquals($doc2->name, $fetchdoc->name, "Name field wrong");
+        
+        $this->_commonDocument->deleteCollection($name);
+    }
+
+    public function testReplaceDocument() 
+    {
+        $data = $this->_getDocumentData();
+        $name = $this->_collectionName("testRD");
+        $this->_commonDocument->createCollection($name);
+        
+        $doc1 = $this->_makeDocument($data[0]);
+        $this->_commonDocument->insertDocument($name, $doc1);
+        $doc2 = $this->_makeDocument($data[1]);
+        $this->_commonDocument->insertDocument($name, $doc2);
+        $this->_wait();
+        
+        $doc3 = $this->_makeDocument($data[2]);
+        $newdoc = new Zend_Cloud_DocumentService_Document($doc3->getFields(), $doc1->getId());
+        $this->_commonDocument->replaceDocument($name, $newdoc);
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc1->getId());
+        $this->assertTrue($fetchdoc instanceof Zend_Cloud_DocumentService_Document, "New document not found");
+        $this->assertEquals($doc3->name, $fetchdoc->name, "Name field did not update");
+        $this->assertEquals($doc3->keyword, $fetchdoc->keyword, "Keywords did not update");
+        
+        $this->_commonDocument->deleteCollection($name);
+    }
+    
+    public function testUpdateDocumentIDFields() 
+    {
+        $data = $this->_getDocumentData();
+        $name = $this->_collectionName("testUD1");
+        $this->_commonDocument->createCollection($name);
+        
+        $doc = $this->_makeDocument($data[0]);
+        $this->_commonDocument->insertDocument($name, $doc);
+        $this->_wait();
+        $doc1 = $this->_makeDocument($data[1]);
+        $this->_commonDocument->updateDocument($name, $doc->getId(), $doc1->getFields());
+        $this->_wait();
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc->getId());
+        $this->assertTrue($fetchdoc instanceof Zend_Cloud_DocumentService_Document, "New document not found");
+        $this->assertEquals($doc1->name, $fetchdoc->name, "Name field did not update");
+        
+         $this->_commonDocument->deleteCollection($name);
+    }
+
+    public function testUpdateDocumentIDDoc() 
+    {
+        $data = $this->_getDocumentData();
+        $name = $this->_collectionName("testUD2");
+        $this->_commonDocument->createCollection($name);
+        // id is specified, fields from another doc
+        $doc1 = $this->_makeDocument($data[1]);
+        $this->_commonDocument->insertDocument($name, $doc1);
+        $doc2 = $this->_makeDocument($data[2]);
+        $this->_commonDocument->updateDocument($name, $doc1->getId(), $doc2);
+        $this->_wait();
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc1->getId());
+        $this->assertTrue($fetchdoc instanceof Zend_Cloud_DocumentService_Document, "New document not found");
+        $this->assertEquals($doc2->name, $fetchdoc->name, "Name field did not update");
+        $this->assertEquals($doc2->keyword, $fetchdoc->keyword, "Keywords did not update");   
+
+         $this->_commonDocument->deleteCollection($name);
+    }
+    
+    public function testUpdateDocumentDoc() 
+    {
+        $data = $this->_getDocumentData();
+        $name = $this->_collectionName("testUD3");
+        $this->_commonDocument->createCollection($name);
+        // id is not specified
+        $doc2 = $this->_makeDocument($data[2]);
+        $doc3 = new Zend_Cloud_DocumentService_Document($this->_makeDocument($data[3])->getFields(), $doc2->getId());
+        $this->_commonDocument->insertDocument($name, $doc2);
+        $this->_wait();
+        $this->_commonDocument->updateDocument($name, null, $doc3);
+        $this->_wait();
+        
+        $fetchdoc = $this->_commonDocument->fetchDocument($name, $doc2->getId());
+        $this->assertTrue($fetchdoc instanceof Zend_Cloud_DocumentService_Document, "New document not found");
+        $this->assertEquals($doc3->name, $fetchdoc->name, "Name field did not update");
+        $this->assertEquals($doc3->keyword, $fetchdoc->keyword, "Keywords did not update");        
+        
+        $this->_commonDocument->deleteCollection($name);
+    }
+   
+    public function testQueryString() 
+    {
+        $name = $this->_collectionName("testQuery");
+        $doc = $this->_loadData($name);
+
+        $query = $this->_queryString($name, $doc[1]->getId(), $doc[2]->getId());
+        $fetchdocs = $this->_commonDocument->query($name, $query);
+
+        $this->assertTrue(count($fetchdocs) >= 2, "Query failed to fetch 2 fields");
+        foreach($fetchdocs as $fdoc) {
+            $this->assertContains($fdoc["name"], array($doc[1]->name, $doc[2]->name), "Wrong name in results");
+            $this->assertContains($fdoc["author"], array($doc[1]->author, $doc[2]->author), "Wrong name in results");
+        }
+
+        $this->_commonDocument->deleteCollection($name);
+    }
+    
+    public function testQueryStruct() 
+    {
+        $name = $this->_collectionName("testStructQuery1");
+        $doc = $this->_loadData($name);
+        
+        // query by ID
+        $query = $this->_commonDocument->select();
+        $this->assertTrue($query instanceof Zend_Cloud_DocumentService_QueryAdapter);
+        $query->from($name)->whereId($doc[1]->getId());
+        $fetchdocs = $this->_commonDocument->query($name, $query);
+        $this->assertEquals(1, count($fetchdocs), 'Query: ' . $query->assemble() . "\nDocuments:\n" . var_export($fetchdocs, 1));
+        foreach ($fetchdocs as $fdoc) {
+            $this->assertEquals($doc[1]->name, $fdoc["name"], "Wrong name in results");
+            $this->assertEquals($doc[1]->author, $fdoc["author"], "Wrong author in results");
+        }
+
+        $this->_commonDocument->deleteCollection($name);
+    }
+        
+    public function testQueryStructWhere() 
+    {
+        $name = $this->_collectionName("testStructQuery2");
+        $doc = $this->_loadData($name);
+        
+        // query by field condition
+        $query = $this->_commonDocument->select()
+            ->from($name)->where("year > ?", array(1945));
+        $fetchdocs = $this->_commonDocument->query($name, $query);
+        $this->assertEquals(3, count($fetchdocs));
+        foreach($fetchdocs as $fdoc) {
+            $this->assertTrue($fdoc["year"] > 1945);
+        }
+
+        $this->_commonDocument->deleteCollection($name);
+    }
+    
+    public function testQueryStructLimit() 
+    {  
+        $name = $this->_collectionName("testStructQuery3");
+        $doc = $this->_loadData($name);
+        
+        // query with limit
+        $query = $this->_commonDocument->select()
+            ->from($name)->where("year > ?", array(1945))->limit(1);
+        $fetchdocs = $this->_commonDocument->query($name, $query);
+        $this->assertEquals(1, count($fetchdocs));
+        foreach($fetchdocs as $fdoc) {
+            $this->assertTrue($fdoc["year"] > 1945);
+            $this->assertContains($fdoc["name"], array($doc[0]->name, $doc[2]->name, $doc[3]->name), "Wrong name in results");
+        }
+        
+        $this->_commonDocument->deleteCollection($name);
+    }
+
+    public function testQueryStructOrder() 
+    {  
+        $name = $this->_collectionName("testStructQuery4");
+        $doc = $this->_loadData($name);
+        
+        // query with sort
+        $query = $this->_commonDocument->select()
+            ->from($name)->where("year > ?", array(1945))->order("year", "desc");
+        $fetchdocs = $this->_commonDocument->query($name, $query);
+        $this->assertEquals(3, count($fetchdocs));
+        foreach ($fetchdocs as $fdoc) {
+            $this->assertEquals($doc[2]->name, $fdoc["name"]);
+            break;
+        }
+
+        $this->_commonDocument->deleteCollection($name);
+    }
+   
+    public function setUp() 
+    {
+        $this->_config = $this->_getConfig();
+        $this->_commonDocument = Zend_Cloud_DocumentService_Factory::getAdapter($this->_config);
+        parent::setUp();
+    } 
+    
+    abstract protected function _getConfig();
+    abstract protected function _getDocumentData();
+    abstract protected function _queryString($domain, $s1, $s2);
+
+    protected function _collectionName($name)
+    {
+        return $this->_dummyCollectionNamePrefix . $name; //.mt_rand();
+    }
+
+    protected function _wait() {
+        sleep($this->_waitPeriod);
+    }
+    
+    protected function _makeDocument($arr)
+    {
+        $id = $arr[self::ID_FIELD];
+        unset($arr[self::ID_FIELD]);
+        return new Zend_Cloud_DocumentService_Document($arr, $id);
+    }
+
+    protected function _loadData($name)
+    {
+        $data = $this->_getDocumentData();
+        $this->_commonDocument->createCollection($name);
+        for($i=0; $i<count($data); $i++) {
+            $doc[$i] = $this->_makeDocument($data[$i]);
+            $this->_commonDocument->insertDocument($name, $doc[$i]);
+        }
+        $this->_wait();
+        return $doc;
+    }
+}

+ 84 - 0
tests/Zend/Cloud/QueueService/Adapter/AllTests.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_QueueService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Cloud_QueueService_Adapter_AllTests::main');
+}
+
+/**
+ * Test helper
+ */
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_QueueService_Adapter_SqsTest
+ */
+require_once 'Zend/Cloud/QueueService/Adapter/SqsTest.php';
+
+/**
+ * @see Zend_Cloud_QueueService_Adapter_WindowsAzureTest
+ */
+require_once 'Zend/Cloud/QueueService/Adapter/WindowsAzureTest.php';
+
+/**
+ * @see Zend_Cloud_QueueService_Adapter_ZendQueueTest
+ */
+require_once 'Zend/Cloud/QueueService/Adapter/ZendQueueTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud_QueueService_Adapter
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Adapter_AllTests
+{
+    /**
+     * Runs this test suite
+     *
+     * @return void
+     */
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    /**
+     * Creates and returns this test suite
+     *
+     * @return PHPUnit_Framework_TestSuite
+     */
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Cloud - QueueService - Adapter');
+
+        $suite->addTestSuite('Zend_Cloud_QueueService_Adapter_SqsTest');
+        $suite->addTestSuite('Zend_Cloud_QueueService_Adapter_WindowsAzureTest');
+        $suite->addTestSuite('Zend_Cloud_QueueService_Adapter_ZendQueueTest');
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_QueueService_Adapter_AllTests::main') {
+    Zend_Cloud_QueueService_Adapter_AllTests::main();
+}

+ 155 - 0
tests/Zend/Cloud/QueueService/Adapter/SqsTest.php

@@ -0,0 +1,155 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_QueueService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_QueueService_Adapter_SqsTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_QueueService_Adapter_SqsTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_QueueServiceTestCase
+ */
+require_once 'Zend/Cloud/QueueService/TestCase.php';
+
+/**
+ * @see Zend_Cloud_QueueeService_Adapter_Sqs
+ */
+require_once 'Zend/Cloud/QueueService/Adapter/Sqs.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Adapter_SqsTest extends Zend_Cloud_QueueService_TestCase
+{
+    /**
+     * Period to wait for propagation in seconds
+     * Should be set by adapter
+     *
+     * @var int
+     */
+    protected $_waitPeriod = 10;
+	protected $_clientType = 'Zend_Service_Amazon_Sqs';
+    
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Sets up this test case
+     *
+     * @return void
+     */
+    public function setUp() 
+    {
+        parent::setUp();
+        // Isolate the tests from slow deletes
+        $this->_wait();
+    }
+
+    public function testListQueues()
+    {
+        try {
+            $queues = $this->_commonQueue->listQueues();
+            $this->_wait();
+            if (count($queues)) {
+                foreach ($queues as $queue) {
+                    $this->_commonQueue->deleteQueue($queue);
+                    $this->_wait();
+                }
+            }
+
+            $queueURL1 = $this->_commonQueue->createQueue('test-list-queue1');
+            $this->assertNotNull($queueURL1);
+            $this->_wait();
+
+            $queueURL2 = $this->_commonQueue->createQueue('test-list-queue2');
+            $this->assertNotNull($queueURL2);
+
+            // Wait 30s after creation to ensure we can retrieve it
+            $this->_wait(30);
+
+            $queues = $this->_commonQueue->listQueues();
+            $errorMessage = "Final queues are ";
+            foreach ($queues as $queue) {
+                $errorMessage .= $queue . ', ';
+            }
+            $errorMessage .= "\nHave queue URLs $queueURL1 and $queueURL2\n";
+            $this->assertEquals(2, count($queues), $errorMessage);
+
+            // PHPUnit does an identical comparison for assertContains(), so we just
+            // use assertTrue and in_array()
+            $this->assertTrue(in_array($queueURL1, $queues));
+            $this->assertTrue(in_array($queueURL2, $queues));
+
+            $this->_commonQueue->deleteQueue($queueURL1);
+            $this->_commonQueue->deleteQueue($queueURL2);
+        } catch (Exception $e) {
+            if (isset($queueURL1)) {
+                $this->_commonQueue->deleteQueue($queueURL1);
+            }
+            if (isset($queueURL2)) {
+                $this->_commonQueue->deleteQueue($queueURL2);
+            }
+            throw $e;
+        }
+    }
+
+    public function testStoreQueueMetadata() {
+        $this->markTestSkipped('SQS does not currently support storing metadata');
+    }
+
+    protected function _getConfig() 
+    {
+        if (!defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ENABLED') 
+            || !constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ENABLED') 
+            || !defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID') 
+            || !defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY')
+        ) {
+            $this->markTestSkipped("Amazon SQS access not configured, skipping test");        
+        }        
+        
+        $config = new Zend_Config(array(
+            Zend_Cloud_QueueService_Factory::QUEUE_ADAPTER_KEY => 'Zend_Cloud_QueueService_Adapter_Sqs',
+            Zend_Cloud_QueueService_Adapter_Sqs::AWS_ACCESS_KEY => constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID'),
+            Zend_Cloud_QueueService_Adapter_Sqs::AWS_SECRET_KEY => constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY'),
+            ));
+
+        return $config;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_QueueService_Adapter_SqsTest::main') {
+    Zend_Cloud_QueueService_Adapter_SqsTest::main();
+}

+ 105 - 0
tests/Zend/Cloud/QueueService/Adapter/WindowsAzureTest.php

@@ -0,0 +1,105 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_Queue
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_QueueService_Adapter_WindowsAzureTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_QueueService_Adapter_WindowsAzureTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_QueueServiceTestCase
+ */
+require_once 'Zend/Cloud/QueueService/TestCase.php';
+
+/**
+ * @see Zend_Cloud_QueueeService_Adapter_WindowsAzure
+ */
+require_once 'Zend/Cloud/QueueService/Adapter/WindowsAzure.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Adapter_WindowsAzureTest extends Zend_Cloud_QueueService_TestCase
+{
+    /**
+     * Period to wait for propagation in seconds
+     * Should be set by adapter
+     *
+     * @var int
+     */
+    protected $_waitPeriod = 10;
+	protected $_clientType = 'Zend_Service_WindowsAzure_Storage_Queue';
+    
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Sets up this test case
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        parent::setUp();
+        $this->_wait();
+    }    
+    
+    protected function _getConfig() 
+    {
+        if (!defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ENABLED') ||
+            !constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ENABLED') ||
+            !defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTNAME') ||
+            !defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTKEY')) {
+            $this->markTestSkipped("Windows Azure access not configured, skipping test");        
+        }        
+        
+        $config = new Zend_Config(array(
+            Zend_Cloud_QueueService_Factory::QUEUE_ADAPTER_KEY => 'Zend_Cloud_QueueService_Adapter_WindowsAzure',
+            Zend_Cloud_QueueService_Adapter_WindowsAzure::ACCOUNT_NAME => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTNAME'),
+            Zend_Cloud_QueueService_Adapter_WindowsAzure::ACCOUNT_KEY => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTKEY'),
+            Zend_Cloud_QueueService_Adapter_WindowsAzure::HOST => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_QUEUE_HOST'),
+            Zend_Cloud_QueueService_Adapter_WindowsAzure::PROXY_HOST => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_HOST'),
+            Zend_Cloud_QueueService_Adapter_WindowsAzure::PROXY_PORT => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_PORT'),
+            Zend_Cloud_QueueService_Adapter_WindowsAzure::PROXY_CREDENTIALS => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_CREDENTIALS'),
+        ));
+
+        return $config;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_QueueService_Adapter_WindowsAzureTest::main') {
+    Zend_Cloud_QueueService_Adapter_WindowsAzureTest::main();
+}

+ 89 - 0
tests/Zend/Cloud/QueueService/Adapter/ZendQueueTest.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_Queue
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_QueueService_Adapter_ZendQueueTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_QueueService_Adapter_ZendQueueTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_QueueServiceTestCase
+ */
+require_once 'Zend/Cloud/QueueService/TestCase.php';
+
+/**
+ * @see Zend_Cloud_QueueeService_Adapter_ZendQueue
+ */
+require_once 'Zend/Cloud/QueueService/Adapter/ZendQueue.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_QueueService_Adapter_ZendQueueTest 
+    extends Zend_Cloud_QueueService_TestCase
+{
+    /**
+     * Period to wait for propagation in seconds
+     * Should be set by adapter
+     *
+     * @var int
+     */
+    protected $_waitPeriod = 0;
+	protected $_clientType = 'Zend_Queue';
+    
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    public function testPeekMessages() 
+    {
+        $this->markTestSkipped('ZendQueue does not currently support peeking messages');
+    }
+    
+    protected function _getConfig() 
+    {        
+        $config = new Zend_Config(array(
+            Zend_Cloud_QueueService_Factory::QUEUE_ADAPTER_KEY => 'Zend_Cloud_QueueService_Adapter_ZendQueue',
+            Zend_Cloud_QueueService_Adapter_ZendQueue::ADAPTER => 'Array'
+        ));
+
+        return $config;
+    }
+    
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_QueueService_Adapter_ZendQueueTest::main') {
+    Zend_Cloud_QueueService_Adapter_ZendQueueTest::main();
+}

+ 63 - 0
tests/Zend/Cloud/QueueService/AllTests.php

@@ -0,0 +1,63 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Cloud_Queue_AllTests::main');
+}
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+require_once 'Zend/Cloud/QueueService/Adapter/AllTests.php';
+
+/**
+ * @see Zend_Cloud_QueueService_FactoryTest
+ */
+require_once 'Zend/Cloud/QueueService/FactoryTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Cloud
+ */
+class Zend_Cloud_QueueService_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Cloud - QueueService');
+
+        $suite->addTestSuite('Zend_Cloud_QueueService_FactoryTest');
+        $suite->addTest(Zend_Cloud_QueueService_Adapter_AllTests::suite());
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_QueueService_AllTests::main') {
+    Zend_Cloud_QueueService_AllTests::main();
+}

+ 103 - 0
tests/Zend/Cloud/QueueService/FactoryTest.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_StorageService_FactoryTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_StorageService_FactoryTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+/**
+ * @see Zend_Config_Ini
+ */
+require_once 'Zend/Config/Ini.php';
+
+/**
+ * @see Zend_Cloud_QueueService_Factory
+ */
+require_once 'Zend/Cloud/QueueService/Factory.php';
+
+require_once 'Zend/Cloud/QueueService/Adapter/ZendQueue.php';
+
+/**
+ * Test class for Zend_Cloud_QueueService_Factory
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Cloud
+ */
+class Zend_Cloud_QueueService_FactoryTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Runs the test methods of this class.
+     *
+     * @return void
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    public function testGetQueueAdapterKey()
+    {
+        $this->assertTrue(is_string(Zend_Cloud_QueueService_Factory::QUEUE_ADAPTER_KEY));
+    }
+
+    public function testGetAdapterWithConfig() 
+    {
+        // SQS adapter
+        $sqsConfig = new Zend_Config_Ini(realpath(dirname(__FILE__) . '/_files/config/sqs.ini'));
+        $sqsAdapter = Zend_Cloud_QueueService_Factory::getAdapter($sqsConfig);
+        $this->assertEquals('Zend_Cloud_QueueService_Adapter_Sqs', get_class($sqsAdapter));
+
+        // zend queue adapter
+        $zqConfig = new Zend_Config_Ini(realpath(dirname(__FILE__) . '/_files/config/zendqueue.ini'));
+        $zq = Zend_Cloud_QueueService_Factory::getAdapter($zqConfig);
+        $this->assertEquals('Zend_Cloud_QueueService_Adapter_ZendQueue', get_class($zq));
+
+        // Azure adapter
+        $azureConfig = new Zend_Config_Ini(realpath(dirname(__FILE__) . '/_files/config/windowsazure.ini'));
+        $azureAdapter = Zend_Cloud_QueueService_Factory::getAdapter($azureConfig);
+        $this->assertEquals('Zend_Cloud_QueueService_Adapter_WindowsAzure', get_class($azureAdapter));
+    }
+
+    public function testGetAdapterWithArray() 
+    {
+        // No need to overdo it; we'll test the array config with just one adapter.
+        $zqConfig = array(Zend_Cloud_QueueService_Factory::QUEUE_ADAPTER_KEY =>
+        					     'Zend_Cloud_QueueService_Adapter_ZendQueue',
+                            Zend_Cloud_QueueService_Adapter_ZendQueue::ADAPTER => "Array");
+
+        $zq = Zend_Cloud_QueueService_Factory::getAdapter($zqConfig);
+
+        $this->assertEquals('Zend_Cloud_QueueService_Adapter_ZendQueue', get_class($zq));
+    }
+}
+
+// Call Zend_Cloud_QueueService_FactoryTest::main() if this source file is executed directly.
+if (PHPUnit_MAIN_METHOD == "Zend_Cloud_QueueService_FactoryTest::main") {
+    Zend_Cloud_QueueService_FactoryTest::main();
+}

+ 352 - 0
tests/Zend/Cloud/QueueService/TestCase.php

@@ -0,0 +1,352 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+/**
+ * @see Zend_Cloud_QueueService_Adapter
+ */
+require_once 'Zend/Cloud/QueueService/Adapter.php';
+
+/**
+ * @see Zend_Config
+ */
+require_once 'Zend/Config.php';
+
+/**
+ * @see Zend_Cloud_Queue_Factory
+ */
+require_once 'Zend/Cloud/QueueService/Factory.php';
+
+/**
+ * This class forces the adapter tests to implement tests for all methods on
+ * Zend_Cloud_QueueService.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Cloud_QueueService_TestCase extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Reference to queue adapter to test
+     *
+     * @var Zend_Cloud_QueueService
+     */
+    protected $_commonQueue;
+    protected $_dummyNamePrefix = '/TestItem';
+    protected $_dummyDataPrefix = 'TestData';
+	protected $_clientType = 'stdClass';
+    /**
+     * Config object
+     *
+     * @var Zend_Config
+     */
+
+    protected $_config;
+
+    /**
+     * Period to wait for propagation in seconds
+     * Should be set by adapter
+     *
+     * @var int
+     */
+    protected $_waitPeriod = 1;
+
+    public function setUp()
+    {
+        $this->_config = $this->_getConfig();
+        $this->_commonQueue = Zend_Cloud_QueueService_Factory::getAdapter($this->_config);
+    }
+    
+    public function testGetClient()
+    {
+    	$this->assertTrue(is_a($this->_commonQueue->getClient(), $this->_clientType)); 
+    }
+    
+    public function testCreateQueue()
+    {
+        try {
+            // Create timeout default should be 30 seconds
+            $startTime = time();
+            $queueURL = $this->_commonQueue->createQueue('test-create-queue');
+            $endTime = time();
+            $this->assertNotNull($queueURL);
+            $this->assertLessThan(30, $endTime - $startTime);
+            $this->_commonQueue->deleteQueue($queueURL);
+        } catch (Exception $e) {
+            if(isset($queueURL)) $this->_commonQueue->deleteQueue($queueURL);
+            throw $e;
+        }
+    }
+
+    public function testDeleteQueue()
+    {
+        try {
+            $queueURL = $this->_commonQueue->createQueue('test-delete-queue');
+            $this->assertNotNull($queueURL);
+
+            $this->_commonQueue->deleteQueue($queueURL);
+
+            $this->_wait();
+            $this->_wait();
+            $this->_wait();
+            try {
+                $messages = $this->_commonQueue->receiveMessages($queueURL);
+                $this->fail('An exception should have been thrown if the queue has been deleted; received ' . var_export($messages, 1));
+            } catch(Zend_Cloud_QueueService_Exception $e) {
+                $this->assertTrue(true);
+                $this->_commonQueue->deleteQueue($queueURL);
+                return;
+            }
+        } catch (Exception $e) {
+            if(isset($queueURL)) $this->_commonQueue->deleteQueue($queueURL);
+            throw $e;
+        }
+    }
+
+    public function testListQueues()
+    {
+        try {
+            $queues = $this->_commonQueue->listQueues();
+            $this->_wait();
+            if (count($queues)) {
+                foreach ($queues as $queue) {
+                    $this->_commonQueue->deleteQueue($queue);
+                    $this->_wait();
+                }
+            }
+
+            $queueURL1 = $this->_commonQueue->createQueue('test-list-queue1');
+            $this->assertNotNull($queueURL1);
+            $this->_wait();
+
+            $queueURL2 = $this->_commonQueue->createQueue('test-list-queue2');
+            $this->assertNotNull($queueURL2);
+            $this->_wait();
+
+            $queues = $this->_commonQueue->listQueues();
+            $errorMessage = "Final queues are ";
+            foreach ($queues as $queue) {
+                $errorMessage .= $queue . ', ';
+            }
+            $errorMessage .= "\nHave queue URLs $queueURL1 and $queueURL2\n";
+            $this->assertEquals(2, count($queues), $errorMessage);
+
+            // PHPUnit does an identical comparison for assertContains(), so we just
+            // use assertTrue and in_array()
+            $this->assertTrue(in_array($queueURL1, $queues));
+            $this->assertTrue(in_array($queueURL2, $queues));
+
+            $this->_commonQueue->deleteQueue($queueURL1);
+            $this->_commonQueue->deleteQueue($queueURL2);
+        } catch (Exception $e) {
+            if (isset($queueURL1)) {
+                $this->_commonQueue->deleteQueue($queueURL1);
+            }
+            if (isset($queueURL2)) {
+                $this->_commonQueue->deleteQueue($queueURL2);
+            }
+            throw $e;
+        }
+    }
+
+    public function testStoresAndFetchesQueueMetadata()
+    {
+        try {
+            $queueURL = $this->_commonQueue->createQueue('test-fetch-queue-metadata');
+            $this->assertNotNull($queueURL);
+            $this->_wait();
+            $this->_commonQueue->storeQueueMetadata($queueURL, array('purpose' => 'test'));
+            $this->_wait();
+            $metadata = $this->_commonQueue->fetchQueueMetadata($queueURL);
+            $this->assertTrue(is_array($metadata));
+            $this->assertGreaterThan(0, count($metadata));
+            $this->_commonQueue->deleteQueue($queueURL);
+        } catch (Exception $e) {
+            if (isset($queueURL)) {
+                $this->_commonQueue->deleteQueue($queueURL);
+            }
+            throw $e;
+        }
+    }
+
+    public function testSendMessage()
+    {
+        try {
+            $queueURL = $this->_commonQueue->createQueue('test-send-message');
+            $this->assertNotNull($queueURL);
+            $this->_wait();
+            $message = 'testSendMessage - Message 1';
+            $this->_commonQueue->sendMessage($queueURL, $message);
+            $this->_wait();
+            $receivedMessages = $this->_commonQueue->receiveMessages($queueURL);
+            $this->assertType('Zend_Cloud_QueueService_MessageSet', $receivedMessages);
+            $this->assertEquals(1, count($receivedMessages));
+            foreach ($receivedMessages as $m) {
+                $this->assertEquals($message, $m->getBody());
+            }
+		  $this->_commonQueue->deleteQueue($queueURL);
+        } catch (Exception $e) {
+            if(isset($queueURL)) $this->_commonQueue->deleteQueue($queueURL);
+            throw $e;
+        }
+    }
+
+    public function testReceiveMessages()
+    {
+        $queueURL = null;
+        try {
+            $queueURL = $this->_commonQueue->createQueue('test-receive-messages');
+            $this->assertNotNull($queueURL);
+            $this->_wait();
+
+            $message1 = 'testReceiveMessages - Message 1';
+            $message2 = 'testReceiveMessages - Message 2';
+            $this->_commonQueue->sendMessage($queueURL, $message1);
+            $this->_commonQueue->sendMessage($queueURL, $message2);
+            $this->_wait();
+            $this->_wait();
+
+            // receive one message
+            $receivedMessages1 = $this->_commonQueue->receiveMessages($queueURL);
+            $this->assertType('Zend_Cloud_QueueService_MessageSet', $receivedMessages1);
+            $this->assertEquals(1, count($receivedMessages1));
+            foreach ($receivedMessages1 as $receivedMessage1) {
+                $this->assertType('Zend_Cloud_QueueService_Message', $receivedMessage1);
+            }
+
+            // cleanup the queue
+            foreach ($receivedMessages1 as $message) {
+                $this->_commonQueue->deleteMessage($queueURL, $message);
+            }
+            $this->_wait();
+            $this->_wait();
+
+            // send 2 messages again
+            $this->_commonQueue->sendMessage($queueURL, $message1);
+            $this->_commonQueue->sendMessage($queueURL, $message2);
+            $this->_wait();
+            $this->_wait();
+            $this->_wait();
+            $this->_wait();
+
+            // receive both messages
+            $receivedMessages2 = $this->_commonQueue->receiveMessages($queueURL, 2);
+            $this->assertType('Zend_Cloud_QueueService_MessageSet', $receivedMessages2);
+            $this->assertEquals(2, count($receivedMessages2));
+
+            $tests = array();
+            foreach ($receivedMessages2 as $message) {
+                $tests[] = $message;
+            }
+            $texts = array($tests[0]->getBody(), $tests[1]->getBody());
+            $this->assertContains($message1, $texts);
+            $this->assertContains($message2, $texts);
+            
+            $this->_commonQueue->deleteQueue($queueURL);
+        } catch (Exception $e) {
+            if (isset($queueURL)) {
+                $this->_commonQueue->deleteQueue($queueURL);
+            }
+            throw $e;
+        }
+    }
+
+    public function testDeleteMessage()
+    {
+        try {
+            $queueURL = $this->_commonQueue->createQueue('test-delete-messages');
+            $this->assertNotNull($queueURL);
+            $this->_wait();
+            $this->_wait();
+            $message1 = 'testDeleteMessage - Message 1';
+            $this->_commonQueue->sendMessage($queueURL, $message1);
+            $this->_wait();
+            $this->_wait();
+            $receivedMessages1 = $this->_commonQueue->receiveMessages($queueURL);
+
+            // should receive one $message1
+            $this->assertType('Zend_Cloud_QueueService_MessageSet', $receivedMessages1);
+            $this->assertEquals(1, count($receivedMessages1));
+            foreach ($receivedMessages1 as $receivedMessage1) {
+                $this->assertEquals($message1, $receivedMessage1->getBody());
+            }
+            $this->_commonQueue->deleteMessage($queueURL, $receivedMessage1);
+            $this->_wait();
+            $this->_wait();
+
+            // now there should be no messages left
+            $receivedMessages2 = $this->_commonQueue->receiveMessages($queueURL);
+            $this->assertType('Zend_Cloud_QueueService_MessageSet', $receivedMessages2);
+		    $this->assertEquals(0, count($receivedMessages2));
+			
+		    $this->_commonQueue->deleteQueue($queueURL);
+        } catch (Exception $e) {
+            if(isset($queueURL)) $this->_commonQueue->deleteQueue($queueURL);
+            throw $e;
+        }
+    }
+
+    public function testPeekMessages()
+    {
+        try {
+            $queueURL = $this->_commonQueue->createQueue('test-peek-messages');
+            $this->assertNotNull($queueURL);
+            $this->_wait();
+            $message1 = 'testPeekMessage - Message 1';
+            $this->_commonQueue->sendMessage($queueURL, $message1);
+            $this->_wait();
+            $peekedMessages = $this->_commonQueue->peekMessages($queueURL, 1);
+            foreach ($peekedMessages as $message) {
+                $this->assertEquals($message1, $message->getBody());
+                break;
+            }
+            // and again
+            $peekedMessages = $this->_commonQueue->peekMessages($queueURL, 1);
+            foreach ($peekedMessages as $message) {
+                $this->assertEquals($message1, $message->getBody());
+                break;
+            }
+
+            $this->_commonQueue->deleteQueue($queueURL);
+        } catch (Exception $e) {
+            if(isset($queueURL)) $this->_commonQueue->deleteQueue($queueURL);
+            throw $e;
+        }
+    }
+
+    protected function _wait($duration = null)
+    {
+        if (null === $duration) {
+            $duration = $this->_waitPeriod;
+        }
+        sleep($duration);
+    }
+
+    /**
+     * Get adapter configuration for concrete test
+     * 
+     * @returns Zend_Config
+     */
+    abstract protected function _getConfig();
+}

+ 3 - 0
tests/Zend/Cloud/QueueService/_files/config/sqs.ini

@@ -0,0 +1,3 @@
+queue_adapter = "Zend_Cloud_QueueService_Adapter_Sqs"
+aws_accesskey = test
+aws_secretkey = test

+ 8 - 0
tests/Zend/Cloud/QueueService/_files/config/windowsazure.ini

@@ -0,0 +1,8 @@
+queue_adapter = "Zend_Cloud_QueueService_Adapter_WindowsAzure"
+storage_host = "queue.core.windows.net"
+storage_container = test
+storage_accountname = test
+storage_accountkey = test
+storage_proxy_host = ""
+storage_proxy_port = ""
+storage_proxy_credentials = ""

+ 2 - 0
tests/Zend/Cloud/QueueService/_files/config/zendqueue.ini

@@ -0,0 +1,2 @@
+queue_adapter = "Zend_Cloud_QueueService_Adapter_ZendQueue"
+adapter = Array

+ 91 - 0
tests/Zend/Cloud/StorageService/Adapter/AllTests.php

@@ -0,0 +1,91 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_StorageService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Cloud_StorageService_Adapter_AllTests::main');
+}
+
+/**
+ * Test helper
+ */
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Adapter_WindowsAzureTest
+ */
+require_once 'Zend/Cloud/StorageService/Adapter/WindowsAzureTest.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Adapter_S3Test
+ */
+require_once 'Zend/Cloud/StorageService/Adapter/S3Test.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Adapter_NirvanixTest
+ */
+require_once 'Zend/Cloud/StorageService/Adapter/NirvanixTest.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Adapter_FileSystemTest
+ */
+require_once 'Zend/Cloud/StorageService/Adapter/FileSystemTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud_StorageService_Adapter
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_AllTests
+{
+    /**
+     * Runs this test suite
+     *
+     * @return void
+     */
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    /**
+     * Creates and returns this test suite
+     *
+     * @return PHPUnit_Framework_TestSuite
+     */
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Cloud');
+
+        $suite->addTestSuite('Zend_Cloud_StorageService_Adapter_WindowsAzureTest');
+        $suite->addTestSuite('Zend_Cloud_StorageService_Adapter_NirvanixTest');
+        $suite->addTestSuite('Zend_Cloud_StorageService_Adapter_FileSystemTest');
+        $suite->addTestSuite('Zend_Cloud_StorageService_Adapter_S3Test');
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_StorageService_Adapter_AllTests::main') {
+    Zend_Cloud_StorageService_Adapter_AllTests::main();
+}

+ 155 - 0
tests/Zend/Cloud/StorageService/Adapter/FileSystemTest.php

@@ -0,0 +1,155 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_StorageService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_StorageService_Adapter_FileSystemTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_StorageService_Adapter_FileSystemTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_StorageService_TestCase
+ */
+require_once 'Zend/Cloud/StorageService/TestCase.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Adapter_FileSystem
+ */
+require_once 'Zend/Cloud/StorageService/Adapter/FileSystem.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_FileSystemTest 
+    extends Zend_Cloud_StorageService_TestCase
+{
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Sets up this test case
+     *
+     * @return void
+     */
+    public function setUp() 
+    {
+        parent::setUp();
+        // No need to wait
+        $this->_waitPeriod = 0;
+        $path = $this->_config->local_directory;
+
+        // If the test directory exists, remove it and replace it
+        if (file_exists($path)) {
+            $this->_rmRecursive($path);
+        } 
+        mkdir($path, 0755);
+    }
+    
+    public function testGetClient()
+    {
+    	$this->assertTrue(is_string($this->_commonStorage->getClient())); 
+    }
+    
+    public function testNoParams()
+    {
+		$this->markTestSkipped('No config params needed for FileSystem');
+    }
+
+    // TODO: Create a custom test for FileSystem that checks fetchMetadata() with file system MD.
+    public function testFetchMetadata() 
+    {
+        $this->markTestIncomplete('FileSystem doesn\'t support writable metadata.');
+    }
+
+    public function testStoreMetadata() 
+    {
+        $this->markTestSkipped('FileSystem doesn\'t support writable metadata.');
+    }
+
+    public function testDeleteMetadata() 
+    {
+        $this->markTestSkipped('FileSystem doesn\'t support writable metadata.');
+    }
+
+	/**
+     * Tears down this test case
+     *
+     * @return void
+     */
+    public function tearDown() 
+    {
+        $path = $this->_config->local_directory;
+
+        // If the test directory exists, remove it
+        if(file_exists($path)) {
+            $this->_rmRecursive($path);
+        }
+
+        parent::tearDown();
+    }
+
+    protected function _rmRecursive($path) 
+    {
+        // Tidy up the path
+        $path = realpath($path);
+
+        if (!file_exists($path)) {
+            return true;
+        } else if (!is_dir($path)) {
+            return unlink($path);
+        } else {
+            foreach (scandir($path) as $item) {
+                if (!($item == '.' || $item == '..')) {
+                    $this->_rmRecursive($item);
+                }
+            }
+            return rmdir($path);
+        }
+    }
+
+    protected function _getConfig() 
+    {
+        $config = new Zend_Config(array(
+            Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY        => 'Zend_Cloud_StorageService_Adapter_Filesystem',
+            Zend_Cloud_StorageService_Adapter_FileSystem::LOCAL_DIRECTORY => dirname(__FILE__) . '/../_files/data/FileSystemTest',
+        ));
+
+        return $config;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_StorageService_Adapter_FileSystemTest::main') {
+    Zend_Cloud_StorageService_Adapter_FileSystemTest::main();
+}

+ 110 - 0
tests/Zend/Cloud/StorageService/Adapter/NirvanixTest.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_StorageService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_StorageService_Adapter_NirvanixTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_StorageService_Adapter_NirvanixTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_StorageService_TestCase
+ */
+require_once 'Zend/Cloud/StorageService/TestCase.php';
+/**
+ * @see Zend_Cloud_StorageService_Adapter_Nirvanix
+ */
+require_once 'Zend/Cloud/StorageService/Adapter/Nirvanix.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_NirvanixTest extends Zend_Cloud_StorageService_TestCase
+{
+	protected $_clientType = 'Zend_Service_Nirvanix';
+
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    public function testFetchItemStream() 
+    {
+        // The Nirvanix client library doesn't support streams
+        $this->markTestSkipped('The Nirvanix client library doesn\'t support streams.');
+    }
+
+    public function testStoreItemStream() 
+    {
+        // The Nirvanix client library doesn't support streams
+        $this->markTestSkipped('The Nirvanix client library doesn\'t support streams.');
+    }
+
+    /**
+     * Sets up this test case
+     *
+     * @return void
+     */
+    public function setUp() 
+    {
+        parent::setUp();
+        $this->_waitPeriod = 5;
+    }
+
+    protected function _getConfig() 
+    {
+        if (!defined('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_ENABLED') 
+            || !constant('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_ENABLED') 
+            || !defined('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_USERNAME') 
+            || !defined('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_ACCESSKEY') 
+            || !defined('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_PASSWORD') 
+            || !defined('TESTS_ZEND_CLOUD_STORAGE_NIRVANIX_DIRECTORY')
+        ) {
+            $this->markTestSkipped("Windows Azure access not configured, skipping test");        
+        }        
+        
+        $config = new Zend_Config(array(
+            Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY       => 'Zend_Cloud_StorageService_Adapter_Nirvanix',
+            Zend_Cloud_StorageService_Adapter_Nirvanix::USERNAME         => constant('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_USERNAME'),
+            Zend_Cloud_StorageService_Adapter_Nirvanix::APP_KEY          => constant('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_ACCESSKEY'),
+            Zend_Cloud_StorageService_Adapter_Nirvanix::PASSWORD         => constant('TESTS_ZEND_SERVICE_NIRVANIX_ONLINE_PASSWORD'),
+            Zend_Cloud_StorageService_Adapter_Nirvanix::REMOTE_DIRECTORY => constant('TESTS_ZEND_CLOUD_STORAGE_NIRVANIX_DIRECTORY'),
+        ));
+        
+        return $config;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_StorageService_Adapter_NirvanixTest::main') {
+    Zend_Cloud_StorageService_Adapter_NirvanixTest::main();
+}

+ 145 - 0
tests/Zend/Cloud/StorageService/Adapter/S3Test.php

@@ -0,0 +1,145 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_StorageService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_StorageService_Adapter_S3Test::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_StorageService_Adapter_S3Test::main");
+}
+
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_StorageService_TestCase
+ */
+require_once 'Zend/Cloud/StorageService/TestCase.php';
+/**
+ * @see Zend_Cloud_StorageService_Adapter_S3
+ */
+require_once 'Zend/Cloud/StorageService/Adapter/S3.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_S3Test 
+    extends Zend_Cloud_StorageService_TestCase
+{
+	protected $_clientType = 'Zend_Service_Amazon_S3';
+
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Sets up this test case
+     *
+     * @return void
+     */
+    public function setUp() 
+    {
+        parent::setUp();
+
+        // Create the bucket here
+        $s3 = new Zend_Service_Amazon_S3(
+            $this->_config->get(Zend_Cloud_StorageService_Adapter_S3::AWS_ACCESS_KEY), 
+            $this->_config->get(Zend_Cloud_StorageService_Adapter_S3::AWS_SECRET_KEY)
+        );
+
+        $s3->createBucket(
+            $this->_config->get(Zend_Cloud_StorageService_Adapter_S3::BUCKET_NAME)
+        );
+    }
+
+    // TODO: Create a custom test for S3 that checks fetchMetadata() with an object that has custom metadata.
+    public function testFetchMetadata() 
+    {
+        $this->markTestIncomplete('S3 doesn\'t support storing metadata after an item is created.');
+    }
+
+    public function testStoreMetadata() 
+    {
+        $this->markTestSkipped('S3 doesn\'t support storing metadata after an item is created.');
+    }
+
+    public function testDeleteMetadata() 
+    {
+        $this->markTestSkipped('S3 doesn\'t support storing metadata after an item is created.');
+    }
+
+
+	/**
+     * Tears down this test case
+     *
+     * @return void
+     */
+    public function tearDown() 
+    {
+        if (!$this->_config) {
+            return;
+        }
+
+        // Delete the bucket here
+        $s3 = new Zend_Service_Amazon_S3(
+            $this->_config->get(Zend_Cloud_StorageService_Adapter_S3::AWS_ACCESS_KEY), 
+            $this->_config->get(Zend_Cloud_StorageService_Adapter_S3::AWS_SECRET_KEY)
+        );
+        $s3->removeBucket(
+            $this->_config->get(Zend_Cloud_StorageService_Adapter_S3::BUCKET_NAME)
+        );
+        parent::tearDown();
+    }
+
+    protected function _getConfig() 
+    {
+        if (!defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ENABLED') 
+            || !constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ENABLED') 
+            || !defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID') 
+            || !defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY') 
+            || !defined('TESTS_ZEND_SERVICE_AMAZON_S3_BUCKET')
+        ) {
+            $this->markTestSkipped("Amazon S3 access not configured, skipping test");        
+        }        
+        
+        $config = new Zend_Config(array(
+            Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY => 'Zend_Cloud_StorageService_Adapter_S3',
+            Zend_Cloud_StorageService_Adapter_S3::AWS_ACCESS_KEY   => constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID'),
+            Zend_Cloud_StorageService_Adapter_S3::AWS_SECRET_KEY   => constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY'),
+            Zend_Cloud_StorageService_Adapter_S3::BUCKET_NAME      => constant('TESTS_ZEND_SERVICE_AMAZON_S3_BUCKET'),
+        ));
+
+        return $config;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_StorageService_Adapter_S3Test::main') {
+    Zend_Cloud_StorageService_Adapter_S3Test::main();
+}

+ 90 - 0
tests/Zend/Cloud/StorageService/Adapter/WindowsAzureTest.php

@@ -0,0 +1,90 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_StorageService
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_StorageService_Adapter_WindowsAzureTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_StorageService_Adapter_WindowsAzureTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_StorageService_TestCase
+ */
+require_once 'Zend/Cloud/StorageService/TestCase.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Adapter_WindowsAzure
+ */
+require_once 'Zend/Cloud/StorageService/Adapter/WindowsAzure.php';
+
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Cloud_StorageService_Adapter_WindowsAzureTest extends Zend_Cloud_StorageService_TestCase
+{
+	protected $_clientType = 'Zend_Service_WindowsAzure_Storage_Blob';
+	/**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    protected function _getConfig() 
+    {
+        if (!defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ENABLED') 
+            || !constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ENABLED') 
+            || !defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTNAME') 
+            || !defined('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTKEY') 
+            || !defined('TESTS_ZEND_CLOUD_STORAGE_WINDOWSAZURE_CONTAINER')
+        ) {
+            $this->markTestSkipped("Windows Azure access not configured, skipping test");        
+        }        
+        
+        $config = new Zend_Config(array(
+            Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY => 'Zend_Cloud_StorageService_Adapter_WindowsAzure',
+            Zend_Cloud_StorageService_Adapter_WindowsAzure::ACCOUNT_NAME => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTNAME'),
+            Zend_Cloud_StorageService_Adapter_WindowsAzure::ACCOUNT_KEY => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_ACCOUNTKEY'),
+            Zend_Cloud_StorageService_Adapter_WindowsAzure::HOST => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_HOST'),
+            Zend_Cloud_StorageService_Adapter_WindowsAzure::PROXY_HOST => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_HOST'),
+            Zend_Cloud_StorageService_Adapter_WindowsAzure::PROXY_PORT => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_PORT'),
+            Zend_Cloud_StorageService_Adapter_WindowsAzure::PROXY_CREDENTIALS => constant('TESTS_ZEND_SERVICE_WINDOWSAZURE_ONLINE_STORAGE_PROXY_CREDENTIALS'),
+            Zend_Cloud_StorageService_Adapter_WindowsAzure::CONTAINER => constant('TESTS_ZEND_CLOUD_STORAGE_WINDOWSAZURE_CONTAINER'),
+        ));
+
+        return $config;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_StorageService_Adapter_WindowsAzureTest::main') {
+    Zend_Cloud_StorageService_Adapter_WindowsAzureTest::main();
+}

+ 63 - 0
tests/Zend/Cloud/StorageService/AllTests.php

@@ -0,0 +1,63 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Cloud_StorageService_AllTests::main');
+}
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+require_once 'Zend/Cloud/StorageService/Adapter/AllTests.php';
+
+/**
+ * @see Zend_Cloud_StorageService_FactoryTest
+ */
+require_once 'Zend/Cloud/StorageService/FactoryTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Cloud
+ */
+class Zend_Cloud_StorageService_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Cloud_StorageService');
+
+        $suite->addTestSuite('Zend_Cloud_StorageService_FactoryTest');
+        $suite->addTest(Zend_Cloud_StorageService_Adapter_AllTests::suite());
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Cloud_StorageService_AllTests::main') {
+    Zend_Cloud_StorageService_AllTests::main();
+}

+ 156 - 0
tests/Zend/Cloud/StorageService/FactoryTest.php

@@ -0,0 +1,156 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_Cloud_StorageService_FactoryTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Cloud_StorageService_FactoryTest::main");
+}
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+/**
+ * @see Zend_Config_Ini
+ */
+require_once 'Zend/Config/Ini.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Factory
+ */
+require_once 'Zend/Cloud/StorageService/Factory.php';
+
+require_once 'Zend/Cloud/StorageService/Adapter/FileSystem.php';
+
+require_once 'Zend/Cloud/StorageService/Adapter/Nirvanix.php';
+
+require_once 'Zend/Cloud/StorageService/Adapter/S3.php';
+
+require_once 'Zend/Cloud/StorageService/Adapter/WindowsAzure.php';
+
+require_once 'Zend/Http/Client/Adapter/Test.php';
+
+/**
+ * Test class for Zend_Cloud_StorageService_Factory
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Cloud
+ */
+class Zend_Cloud_StorageService_FactoryTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Runs the test methods of this class.
+     *
+     * @return void
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    public function testGetStorageAdapterKey()
+    {
+        $this->assertTrue(is_string(Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY));
+    }
+
+    public function testGetAdapterWithConfig()
+    {
+        $httptest = new Zend_Http_Client_Adapter_Test();
+
+        // Nirvanix adapter
+        $nirvanixConfig = new Zend_Config_Ini(realpath(dirname(__FILE__) . '/_files/config/nirvanix.ini'));
+        $nirvanixConfig = $nirvanixConfig->toArray();
+        $nirvanixConfig[Zend_Cloud_StorageService_Adapter_Nirvanix::HTTP_ADAPTER] = $httptest;
+
+        $doc = new DOMDocument('1.0', 'utf-8');
+        $root = $doc->createElement('Response');
+        $responseCode = $doc->createElement('ResponseCode', 0);
+        $sessionTok   = $doc->createElement('SessionToken', '54592180-7060-4D4B-BC74-2566F4B2F943');
+        $root->appendChild($responseCode);
+        $root->appendChild($sessionTok);
+        $doc->appendChild($root);
+        $body = $doc->saveXML();
+
+        $resp = new Zend_Http_Response(200, array('Date' => 0), $body);
+        $httptest->setResponse($resp);
+        $nirvanixAdapter = Zend_Cloud_StorageService_Factory::getAdapter(
+                                    $nirvanixConfig
+        );
+        $this->assertEquals('Zend_Cloud_StorageService_Adapter_Nirvanix', get_class($nirvanixAdapter));
+
+        // S3 adapter
+        $s3Config = new Zend_Config_Ini(realpath(dirname(__FILE__) . '/_files/config/s3.ini'));
+        $s3Adapter = Zend_Cloud_StorageService_Factory::getAdapter($s3Config);
+        $this->assertEquals('Zend_Cloud_StorageService_Adapter_S3', get_class($s3Adapter));
+
+        // file system adapter
+        $fileSystemConfig = new Zend_Config_Ini(realpath(dirname(__FILE__) . '/_files/config/filesystem.ini'));
+        $fileSystemAdapter = Zend_Cloud_StorageService_Factory::getAdapter($fileSystemConfig);
+        $this->assertEquals('Zend_Cloud_StorageService_Adapter_FileSystem', get_class($fileSystemAdapter));
+
+        // Azure adapter
+        $azureConfig    = new Zend_Config_Ini(realpath(dirname(__FILE__) . '/_files/config/windowsazure.ini'));
+        $azureConfig    = $azureConfig->toArray();
+        $azureContainer = $azureConfig[Zend_Cloud_StorageService_Adapter_WindowsAzure::CONTAINER];
+        $azureConfig[Zend_Cloud_StorageService_Adapter_WindowsAzure::HTTP_ADAPTER] = $httptest;
+        $q = "?";
+
+        $doc = new DOMDocument('1.0', 'utf-8');
+        $root = $doc->createElement('EnumerationResults');
+        $acctName = $doc->createAttribute('AccountName');
+        $acctName->value = 'http://myaccount.blob.core.windows.net';
+        $root->appendChild($acctName);
+        $maxResults     = $doc->createElement('MaxResults', 1);
+        $containers     = $doc->createElement('Containers');
+        $container      = $doc->createElement('Container');
+        $containerName  = $doc->createElement('Name', $azureContainer);
+        $container->appendChild($containerName);
+        $containers->appendChild($container);
+        $root->appendChild($maxResults);
+        $root->appendChild($containers);
+        $doc->appendChild($root);
+        $body = $doc->saveXML();
+
+        $resp = new Zend_Http_Response(200, array('x-ms-request-id' => 0), $body);
+        $httptest->setResponse($resp);
+        $azureAdapter = Zend_Cloud_StorageService_Factory::getAdapter($azureConfig);
+        $this->assertEquals('Zend_Cloud_StorageService_Adapter_WindowsAzure', get_class($azureAdapter));
+    }
+
+    public function testGetAdapterWithArray() 
+    {
+        // No need to overdo it; we'll test the array config with just one adapter.
+        $fileSystemConfig = array(
+            Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY        => 'Zend_Cloud_StorageService_Adapter_FileSystem',
+            Zend_Cloud_StorageService_Adapter_FileSystem::LOCAL_DIRECTORY => dirname(__FILE__) ."/_files/data",
+        );
+        $fileSystemAdapter = Zend_Cloud_StorageService_Factory::getAdapter($fileSystemConfig);
+        $this->assertEquals('Zend_Cloud_StorageService_Adapter_FileSystem', get_class($fileSystemAdapter));
+    }
+}
+
+// Call Zend_Cloud_StorageService_FactoryTest::main() if this source file is executed directly.
+if (PHPUnit_MAIN_METHOD == "Zend_Cloud_StorageService_FactoryTest::main") {
+    Zend_Cloud_StorageService_FactoryTest::main();
+}

+ 411 - 0
tests/Zend/Cloud/StorageService/TestCase.php

@@ -0,0 +1,411 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Cloud_AdapterTestCase
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Adapter
+ */
+require_once 'Zend/Cloud/StorageService/Adapter.php';
+
+/**
+ * @see Zend_Config
+ */
+require_once 'Zend/Config.php';
+
+/**
+ * @see Zend_Cloud_StorageService_Factory
+ */
+require_once 'Zend/Cloud/StorageService/Factory.php';
+
+/**
+ * This class forces the adapter tests to implement tests for all methods on
+ * Zend_Cloud_StorageService
+ *
+ * @category   Zend
+ * @package    Zend_Cloud
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Cloud_StorageService_TestCase extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Reference to storage adapter to test
+     *
+     * @var Zend_Cloud_StorageService
+     */
+    protected $_commonStorage;
+
+    protected $_dummyNamePrefix = 'TestItem';
+
+    protected $_dummyDataPrefix = 'TestData';
+	protected $_clientType = 'stdClass';
+    
+    /**
+     * Config object
+     *
+     * @var Zend_Config
+     */
+
+    protected $_config;
+
+    /**
+     * Period to wait for propagation in seconds
+     * Should be set by adapter
+     *
+     * @var int
+     */
+    protected $_waitPeriod = 1;
+
+    public function setUp()
+    {
+        $this->_config = $this->_getConfig();
+        $this->_commonStorage = Zend_Cloud_StorageService_Factory::getAdapter($this->_config);
+    }
+    
+    public function testGetClient()
+    {
+    	$this->assertTrue(is_a($this->_commonStorage->getClient(), $this->_clientType)); 
+    }
+    
+    public function testNoParams()
+    {
+        $config = array(Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY => $this->_config->get(Zend_Cloud_StorageService_Factory::STORAGE_ADAPTER_KEY));
+        $this->setExpectedException('Zend_Cloud_StorageService_Exception');
+        $s = Zend_Cloud_StorageService_Factory::getAdapter($config);
+    }
+    
+    /**
+     * Test fetch item
+     *
+     * @return void
+     */
+    public function testFetchItemString() 
+    {
+        $dummyNameText   = null;
+        $dummyNameStream = null;
+        try {
+            $originalData  = $this->_dummyDataPrefix . 'FetchItem';
+            $dummyNameText = $this->_dummyNamePrefix . 'ForFetchText';
+            $this->_clobberItem($originalData, $dummyNameText);
+            $this->_wait();
+
+            $returnedData = $this->_commonStorage->fetchItem($dummyNameText);
+            $this->assertEquals($originalData, $returnedData);
+            $this->_commonStorage->deleteItem($dummyNameText);
+            $this->_wait();
+
+            $this->assertFalse($this->_commonStorage->fetchItem($dummyNameText));
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyNameText);
+            } catch (Zend_Cloud_Exception $ignoreMe) {
+            }
+            throw $e;
+        }
+    }
+
+	/**
+     * Test fetch item
+     *
+     * @return void
+     */
+    public function testFetchItemStream() 
+    {
+        // TODO: Add support for streaming fetch
+        return $this->markTestIncomplete('Cloud API doesn\'t support streamed fetches yet');
+        $dummyNameText   = null;
+        $dummyNameStream = null;
+        try {
+            $originalFilename = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '_files/data/dummy_data.txt');
+            $dummyNameStream  = $this->_dummyNamePrefix . 'ForFetchStream';
+            $stream = fopen($originalFilename, 'r');
+            $this->_clobberItem($stream, $dummyNameStream);
+            $this->_wait();
+
+            $returnedData = $this->_commonStorage->fetchItem($dummyNameStream);
+            $this->assertEquals(file_get_contents($originalFilename), $returnedData);
+            $this->_commonStorage->deleteItem($dummyNameStream);
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyNameStream);
+            } catch (Zend_Cloud_Exception $ignoreMe) {
+            }
+            throw $e;
+        }
+    }
+
+    /**
+     * Test store item
+     *
+     * @return void
+     */
+    public function testStoreItemText() 
+    {
+        $dummyNameText = null;
+        try {
+            // Test string data
+            $originalData  = $this->_dummyDataPrefix . 'StoreItem';
+            $dummyNameText = $this->_dummyNamePrefix . 'ForStoreText';
+            $this->_clobberItem($originalData, $dummyNameText);
+            $this->_wait();
+
+            $returnedData = $this->_commonStorage->fetchItem($dummyNameText);
+            $this->assertEquals($originalData, $returnedData);
+            $this->_commonStorage->deleteItem($dummyNameText);
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyNameText);
+            } catch (Zend_Cloud_Exception $ignoreMe) {
+            }
+            throw $e;
+        }
+    }
+
+	/**
+     * Test store item
+     *
+     * @return void
+     */
+    public function testStoreItemStream() 
+    {
+        $dummyNameStream = $this->_dummyNamePrefix . 'ForStoreStream';
+        try {
+            // Test stream data
+            $originalFilename = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '_files/data/dummy_data.txt');
+            $stream = fopen($originalFilename, 'r');
+            $this->_commonStorage->storeItem($dummyNameStream, $stream);
+            $this->_wait();
+
+            $returnedData = $this->_commonStorage->fetchItem($dummyNameStream);
+            $this->assertEquals(file_get_contents($originalFilename), $returnedData);
+            $this->_commonStorage->deleteItem($dummyNameStream);
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyNameStream);
+            } catch (Zend_Cloud_Exception $ignoreMe) {
+            }
+            throw $e;
+        }
+    }
+
+    /**
+     * Test delete item
+     *
+     * @return void
+     */
+    public function testDeleteItem() 
+    {
+        $dummyName = $this->_dummyNamePrefix . 'ForDelete';
+        try {
+            // Test string data
+            $originalData = $this->_dummyDataPrefix . 'DeleteItem';
+            $this->_clobberItem($originalData, $dummyName);
+            $this->_wait();
+
+            $returnedData = $this->_commonStorage->fetchItem($dummyName);
+            $this->assertEquals($originalData, $returnedData);
+            $this->_wait();
+
+            $this->_commonStorage->deleteItem($dummyName);
+            $this->_wait();
+
+            $this->assertFalse($this->_commonStorage->fetchItem($dummyName));
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyName);
+            } catch (Zend_Cloud_Exception $ignorme) {
+            }
+            throw $e;
+        }
+    }
+
+    /**
+     * Test copy item
+     *
+     * @return void
+     */
+    public function testCopyItem() 
+    {
+        $this->markTestSkipped('This test should be re-enabled when the semantics of "copy" change');
+        try {
+            // Test string data
+            $originalData = $this->_dummyDataPrefix . 'CopyItem';
+            $dummyName1 = $this->_dummyNamePrefix . 'ForCopy1';
+            $dummyName2 = $this->_dummyNamePrefix . 'ForCopy2';
+            $this->_clobberItem($originalData, $dummyName1);
+            $this->_wait();
+
+            $returnedData = $this->_commonStorage->fetchItem($dummyName1);
+            $this->assertEquals($originalData, $returnedData);
+            $this->_wait();
+
+            $this->_commonStorage->copyItem($dummyName1, $dummyName2);
+            $copiedData = $this->_commonStorage->fetchItem($dummyName2);
+            $this->assertEquals($originalData, $copiedData);
+            $this->_commonStorage->deleteItem($dummyName1);
+            $this->_commonStorage->fetchItem($dummyName1);
+            $this->_commonStorage->deleteItem($dummyName2);
+            $this->_commonStorage->fetchItem($dummyName2);
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyName1);
+                $this->_commonStorage->deleteItem($dummyName2);
+            } catch (Zend_Cloud_Exception $ignoreme) {
+            }
+            throw $e;
+        }
+    }
+
+	/**
+     * Test move item
+     *
+     * @return void
+     */
+    public function testMoveItem() 
+    {
+        $this->markTestSkipped('This test should be re-enabled when the semantics of "move" change');
+
+        try {
+            // Test string data
+            $originalData = $this->_dummyDataPrefix . 'MoveItem';
+            $dummyName1 = $this->_dummyNamePrefix . 'ForMove1';
+            $dummyName2 = $this->_dummyNamePrefix . 'ForMove2';
+            $this->_clobberItem($originalData, $dummyName1);
+            $this->_wait();
+
+            $this->_commonStorage->moveItem($dummyName1, $dummyName2);
+            $this->_wait();
+
+            $movedData = $this->_commonStorage->fetchItem($dummyName2);
+            $this->assertEquals($originalData, $movedData);
+            $this->assertFalse($this->_commonStorage->fetchItem($dummyName1));
+            $this->_commonStorage->deleteItem($dummyName2);
+            $this->assertFalse($this->_commonStorage->fetchItem($dummyName2));
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyName1);
+                $this->_commonStorage->deleteItem($dummyName2);
+            } catch (Zend_Cloud_Exception $ignoreme) {
+            }
+            throw $e;
+        }
+    }
+
+	/**
+     * Test fetch metadata
+     *
+     * @return void
+     */
+    public function testFetchMetadata() 
+    {
+        try {
+            // Test string data
+            $data = $this->_dummyDataPrefix . 'FetchMetadata';
+            $dummyName = $this->_dummyNamePrefix . 'ForMetadata';
+            $this->_clobberItem($data, $dummyName);
+            $this->_wait();
+
+            $this->_commonStorage->storeMetadata($dummyName, array('zend' => 'zend'));
+            $this->_wait();
+
+            // Hopefully we can assert more about the metadata in the future :/
+            $this->assertTrue(is_array($this->_commonStorage->fetchMetadata($dummyName)));
+            $this->_commonStorage->deleteItem($dummyName);
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyName);
+            } catch (Zend_Cloud_Exception $ignoreme) {
+            }
+            throw $e;
+        }
+    }
+
+	/**
+     * Test list items
+     *
+     * @return void
+     */
+    public function testListItems() 
+    {
+        $dummyName1 = null;
+        $dummyName2 = null;
+        try {
+
+            $dummyName1 = $this->_dummyNamePrefix . 'ForListItem1';
+            $dummyData1 = $this->_dummyDataPrefix . 'Item1';
+            $this->_clobberItem($dummyData1, $dummyName1);
+
+            $dummyName2 = $this->_dummyNamePrefix . 'ForListItem2';
+            $dummyData2 = $this->_dummyDataPrefix . 'Item2';
+            $this->_clobberItem($dummyData2, $dummyName2);
+            $this->_wait();
+
+            $objects = $this->_commonStorage->listItems('');
+
+            $this->assertEquals(2, sizeof($objects));
+
+            // PHPUnit does an identical comparison for assertContains(), so we just
+            // use assertTrue and in_array()
+            $this->assertTrue(in_array($dummyName1, $objects));
+            $this->assertTrue(in_array($dummyName2, $objects));
+
+            $this->_commonStorage->deleteItem($dummyName1);
+            $this->_commonStorage->deleteItem($dummyName2);
+        } catch (Exception $e) {
+            try {
+                $this->_commonStorage->deleteItem($dummyName1);
+                $this->_commonStorage->deleteItem($dummyName2);
+            } catch (Zend_Cloud_Exception $ignoreme) {
+            }
+            throw $e;
+        }
+    }
+
+    protected function _wait() 
+    {
+        sleep($this->_waitPeriod);
+    }
+
+    /**
+     * Put given item at given path
+     * 
+     * Removes old item if it was stored there.
+     * 
+     * @param string $data Data item to place there
+     * @param string $path Path to write
+     */
+    protected function _clobberItem($data, $path) 
+    {
+        if($this->_commonStorage->fetchItem($path)) {
+            $this->_commonStorage->deleteItem($path);
+        }
+        $this->_wait();
+        $this->_commonStorage->storeItem($path, $data);
+    }
+    
+    /**
+     * Get adapter configuration for concrete test
+     * @returns Zend_Config
+     */
+    abstract protected function _getConfig();
+}

+ 2 - 0
tests/Zend/Cloud/StorageService/_files/config/filesystem.ini

@@ -0,0 +1,2 @@
+storage_adapter = "Zend_Cloud_StorageService_Adapter_FileSystem"
+local_directory = "_files/data/FileSystemTest"

+ 5 - 0
tests/Zend/Cloud/StorageService/_files/config/nirvanix.ini

@@ -0,0 +1,5 @@
+storage_adapter = "Zend_Cloud_StorageService_Adapter_Nirvanix"
+auth_accesskey = test
+auth_username  = test
+auth_password  = test
+remote_directory = "/SimpleCloudUnitTests/"

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.