web/lib/Zend/Service/Amazon/SimpleDb.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/Zend/Service/Amazon/SimpleDb.php	Fri Mar 11 15:05:35 2011 +0100
@@ -0,0 +1,580 @@
+<?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) {
+                // attribute value cannot be array, so when several items are passed
+                // they are treated as separate values with the same attribute name
+                foreach($attribute->getValues() as $value) {
+                    $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Name'] = $attribute->getName();
+                    $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Value'] = $value;
+                    if (isset($replace[$name]) 
+                        && isset($replace[$name][$attribute->getName()]) 
+                        && $replace[$name][$attribute->getName()]
+                    ) {
+                        $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Replace'] = 'true';
+                    }
+                    $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     = (trim($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);
+        }
+    }
+}