2013-11-05 11:18:30 +00:00
< ? 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
2014-03-14 13:29:55 +00:00
* @ copyright Copyright ( c ) 2005 - 2014 Zend Technologies USA Inc . ( http :// www . zend . com )
2013-11-05 11:18:30 +00:00
* @ 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
2014-03-14 13:29:55 +00:00
* @ copyright Copyright ( c ) 2005 - 2014 Zend Technologies USA Inc . ( http :// www . zend . com )
2013-11-05 11:18:30 +00:00
* @ 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' );
}
}
}