|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Service_WindowsAzure |
|
17 * @subpackage Storage |
|
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
19 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
20 * @version $Id: Table.php 23170 2010-10-19 18:29:24Z mabe $ |
|
21 */ |
|
22 |
|
23 /** |
|
24 * @see Zend_Service_WindowsAzure_Credentials_CredentialsAbstract |
|
25 */ |
|
26 require_once 'Zend/Service/WindowsAzure/Credentials/CredentialsAbstract.php'; |
|
27 |
|
28 /** |
|
29 * @see Zend_Service_WindowsAzure_Credentials_SharedKey |
|
30 */ |
|
31 require_once 'Zend/Service/WindowsAzure/Credentials/SharedKey.php'; |
|
32 |
|
33 /** |
|
34 * @see Zend_Service_WindowsAzure_Credentials_SharedKeyLite |
|
35 */ |
|
36 require_once 'Zend/Service/WindowsAzure/Credentials/SharedKeyLite.php'; |
|
37 |
|
38 /** |
|
39 * @see Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract |
|
40 */ |
|
41 require_once 'Zend/Service/WindowsAzure/RetryPolicy/RetryPolicyAbstract.php'; |
|
42 |
|
43 /** |
|
44 * @see Zend_Http_Client |
|
45 */ |
|
46 require_once 'Zend/Http/Client.php'; |
|
47 |
|
48 /** |
|
49 * @see Zend_Http_Response |
|
50 */ |
|
51 require_once 'Zend/Http/Response.php'; |
|
52 |
|
53 /** |
|
54 * @see Zend_Service_WindowsAzure_Storage |
|
55 */ |
|
56 require_once 'Zend/Service/WindowsAzure/Storage.php'; |
|
57 |
|
58 /** |
|
59 * @see Zend_Service_WindowsAzure_Storage_BatchStorageAbstract |
|
60 */ |
|
61 require_once 'Zend/Service/WindowsAzure/Storage/BatchStorageAbstract.php'; |
|
62 |
|
63 /** |
|
64 * @see Zend_Service_WindowsAzure_Storage_TableInstance |
|
65 */ |
|
66 require_once 'Zend/Service/WindowsAzure/Storage/TableInstance.php'; |
|
67 |
|
68 /** |
|
69 * @see Zend_Service_WindowsAzure_Storage_TableEntity |
|
70 */ |
|
71 require_once 'Zend/Service/WindowsAzure/Storage/TableEntity.php'; |
|
72 |
|
73 /** |
|
74 * @see Zend_Service_WindowsAzure_Storage_DynamicTableEntity |
|
75 */ |
|
76 require_once 'Zend/Service/WindowsAzure/Storage/DynamicTableEntity.php'; |
|
77 |
|
78 /** |
|
79 * @see Zend_Service_WindowsAzure_Storage_TableEntityQuery |
|
80 */ |
|
81 require_once 'Zend/Service/WindowsAzure/Storage/TableEntityQuery.php'; |
|
82 |
|
83 /** |
|
84 * @see Zend_Service_WindowsAzure_Exception |
|
85 */ |
|
86 require_once 'Zend/Service/WindowsAzure/Exception.php'; |
|
87 |
|
88 |
|
89 /** |
|
90 * @category Zend |
|
91 * @package Zend_Service_WindowsAzure |
|
92 * @subpackage Storage |
|
93 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
94 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
95 */ |
|
96 class Zend_Service_WindowsAzure_Storage_Table |
|
97 extends Zend_Service_WindowsAzure_Storage_BatchStorageAbstract |
|
98 { |
|
99 /** |
|
100 * Creates a new Zend_Service_WindowsAzure_Storage_Table instance |
|
101 * |
|
102 * @param string $host Storage host name |
|
103 * @param string $accountName Account name for Windows Azure |
|
104 * @param string $accountKey Account key for Windows Azure |
|
105 * @param boolean $usePathStyleUri Use path-style URI's |
|
106 * @param Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests |
|
107 */ |
|
108 public function __construct($host = Zend_Service_WindowsAzure_Storage::URL_DEV_TABLE, $accountName = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_ACCOUNT, $accountKey = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_KEY, $usePathStyleUri = false, Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy = null) |
|
109 { |
|
110 parent::__construct($host, $accountName, $accountKey, $usePathStyleUri, $retryPolicy); |
|
111 |
|
112 // Always use SharedKeyLite authentication |
|
113 $this->_credentials = new Zend_Service_WindowsAzure_Credentials_SharedKeyLite($accountName, $accountKey, $this->_usePathStyleUri); |
|
114 |
|
115 // API version |
|
116 $this->_apiVersion = '2009-09-19'; |
|
117 } |
|
118 |
|
119 /** |
|
120 * Check if a table exists |
|
121 * |
|
122 * @param string $tableName Table name |
|
123 * @return boolean |
|
124 */ |
|
125 public function tableExists($tableName = '') |
|
126 { |
|
127 if ($tableName === '') { |
|
128 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); |
|
129 } |
|
130 |
|
131 // List tables |
|
132 $tables = $this->listTables(); // 2009-09-19 does not support $this->listTables($tableName); all of a sudden... |
|
133 foreach ($tables as $table) { |
|
134 if ($table->Name == $tableName) { |
|
135 return true; |
|
136 } |
|
137 } |
|
138 |
|
139 return false; |
|
140 } |
|
141 |
|
142 /** |
|
143 * List tables |
|
144 * |
|
145 * @param string $nextTableName Next table name, used for listing tables when total amount of tables is > 1000. |
|
146 * @return array |
|
147 * @throws Zend_Service_WindowsAzure_Exception |
|
148 */ |
|
149 public function listTables($nextTableName = '') |
|
150 { |
|
151 // Build query string |
|
152 $queryString = array(); |
|
153 if ($nextTableName != '') { |
|
154 $queryString[] = 'NextTableName=' . $nextTableName; |
|
155 } |
|
156 $queryString = self::createQueryStringFromArray($queryString); |
|
157 |
|
158 // Perform request |
|
159 $response = $this->_performRequest('Tables', $queryString, Zend_Http_Client::GET, null, true); |
|
160 if ($response->isSuccessful()) { |
|
161 // Parse result |
|
162 $result = $this->_parseResponse($response); |
|
163 |
|
164 if (!$result || !$result->entry) { |
|
165 return array(); |
|
166 } |
|
167 |
|
168 $entries = null; |
|
169 if (count($result->entry) > 1) { |
|
170 $entries = $result->entry; |
|
171 } else { |
|
172 $entries = array($result->entry); |
|
173 } |
|
174 |
|
175 // Create return value |
|
176 $returnValue = array(); |
|
177 foreach ($entries as $entry) { |
|
178 $tableName = $entry->xpath('.//m:properties/d:TableName'); |
|
179 $tableName = (string)$tableName[0]; |
|
180 |
|
181 $returnValue[] = new Zend_Service_WindowsAzure_Storage_TableInstance( |
|
182 (string)$entry->id, |
|
183 $tableName, |
|
184 (string)$entry->link['href'], |
|
185 (string)$entry->updated |
|
186 ); |
|
187 } |
|
188 |
|
189 // More tables? |
|
190 if ($response->getHeader('x-ms-continuation-NextTableName') !== null) { |
|
191 $returnValue = array_merge($returnValue, $this->listTables($response->getHeader('x-ms-continuation-NextTableName'))); |
|
192 } |
|
193 |
|
194 return $returnValue; |
|
195 } else { |
|
196 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); |
|
197 } |
|
198 } |
|
199 |
|
200 /** |
|
201 * Create table |
|
202 * |
|
203 * @param string $tableName Table name |
|
204 * @return Zend_Service_WindowsAzure_Storage_TableInstance |
|
205 * @throws Zend_Service_WindowsAzure_Exception |
|
206 */ |
|
207 public function createTable($tableName = '') |
|
208 { |
|
209 if ($tableName === '') { |
|
210 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); |
|
211 } |
|
212 |
|
213 // Generate request body |
|
214 $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?> |
|
215 <entry |
|
216 xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" |
|
217 xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" |
|
218 xmlns="http://www.w3.org/2005/Atom"> |
|
219 <title /> |
|
220 <updated>{tpl:Updated}</updated> |
|
221 <author> |
|
222 <name /> |
|
223 </author> |
|
224 <id /> |
|
225 <content type="application/xml"> |
|
226 <m:properties> |
|
227 <d:TableName>{tpl:TableName}</d:TableName> |
|
228 </m:properties> |
|
229 </content> |
|
230 </entry>'; |
|
231 |
|
232 $requestBody = $this->_fillTemplate($requestBody, array( |
|
233 'BaseUrl' => $this->getBaseUrl(), |
|
234 'TableName' => htmlspecialchars($tableName), |
|
235 'Updated' => $this->isoDate(), |
|
236 'AccountName' => $this->_accountName |
|
237 )); |
|
238 |
|
239 // Add header information |
|
240 $headers = array(); |
|
241 $headers['Content-Type'] = 'application/atom+xml'; |
|
242 $headers['DataServiceVersion'] = '1.0;NetFx'; |
|
243 $headers['MaxDataServiceVersion'] = '1.0;NetFx'; |
|
244 |
|
245 // Perform request |
|
246 $response = $this->_performRequest('Tables', '', Zend_Http_Client::POST, $headers, true, $requestBody); |
|
247 if ($response->isSuccessful()) { |
|
248 // Parse response |
|
249 $entry = $this->_parseResponse($response); |
|
250 |
|
251 $tableName = $entry->xpath('.//m:properties/d:TableName'); |
|
252 $tableName = (string)$tableName[0]; |
|
253 |
|
254 return new Zend_Service_WindowsAzure_Storage_TableInstance( |
|
255 (string)$entry->id, |
|
256 $tableName, |
|
257 (string)$entry->link['href'], |
|
258 (string)$entry->updated |
|
259 ); |
|
260 } else { |
|
261 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); |
|
262 } |
|
263 } |
|
264 |
|
265 /** |
|
266 * Delete table |
|
267 * |
|
268 * @param string $tableName Table name |
|
269 * @throws Zend_Service_WindowsAzure_Exception |
|
270 */ |
|
271 public function deleteTable($tableName = '') |
|
272 { |
|
273 if ($tableName === '') { |
|
274 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); |
|
275 } |
|
276 |
|
277 // Add header information |
|
278 $headers = array(); |
|
279 $headers['Content-Type'] = 'application/atom+xml'; |
|
280 |
|
281 // Perform request |
|
282 $response = $this->_performRequest('Tables(\'' . $tableName . '\')', '', Zend_Http_Client::DELETE, $headers, true, null); |
|
283 if (!$response->isSuccessful()) { |
|
284 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); |
|
285 } |
|
286 } |
|
287 |
|
288 /** |
|
289 * Insert entity into table |
|
290 * |
|
291 * @param string $tableName Table name |
|
292 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to insert |
|
293 * @return Zend_Service_WindowsAzure_Storage_TableEntity |
|
294 * @throws Zend_Service_WindowsAzure_Exception |
|
295 */ |
|
296 public function insertEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null) |
|
297 { |
|
298 if ($tableName === '') { |
|
299 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); |
|
300 } |
|
301 if ($entity === null) { |
|
302 throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.'); |
|
303 } |
|
304 |
|
305 // Generate request body |
|
306 $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?> |
|
307 <entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom"> |
|
308 <title /> |
|
309 <updated>{tpl:Updated}</updated> |
|
310 <author> |
|
311 <name /> |
|
312 </author> |
|
313 <id /> |
|
314 <content type="application/xml"> |
|
315 <m:properties> |
|
316 {tpl:Properties} |
|
317 </m:properties> |
|
318 </content> |
|
319 </entry>'; |
|
320 |
|
321 $requestBody = $this->_fillTemplate($requestBody, array( |
|
322 'Updated' => $this->isoDate(), |
|
323 'Properties' => $this->_generateAzureRepresentation($entity) |
|
324 )); |
|
325 |
|
326 // Add header information |
|
327 $headers = array(); |
|
328 $headers['Content-Type'] = 'application/atom+xml'; |
|
329 |
|
330 // Perform request |
|
331 $response = null; |
|
332 if ($this->isInBatch()) { |
|
333 $this->getCurrentBatch()->enlistOperation($tableName, '', Zend_Http_Client::POST, $headers, true, $requestBody); |
|
334 return null; |
|
335 } else { |
|
336 $response = $this->_performRequest($tableName, '', Zend_Http_Client::POST, $headers, true, $requestBody); |
|
337 } |
|
338 if ($response->isSuccessful()) { |
|
339 // Parse result |
|
340 $result = $this->_parseResponse($response); |
|
341 |
|
342 $timestamp = $result->xpath('//m:properties/d:Timestamp'); |
|
343 $timestamp = (string)$timestamp[0]; |
|
344 |
|
345 $etag = $result->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'); |
|
346 $etag = (string)$etag['etag']; |
|
347 |
|
348 // Update properties |
|
349 $entity->setTimestamp($timestamp); |
|
350 $entity->setEtag($etag); |
|
351 |
|
352 return $entity; |
|
353 } else { |
|
354 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); |
|
355 } |
|
356 } |
|
357 |
|
358 /** |
|
359 * Delete entity from table |
|
360 * |
|
361 * @param string $tableName Table name |
|
362 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to delete |
|
363 * @param boolean $verifyEtag Verify etag of the entity (used for concurrency) |
|
364 * @throws Zend_Service_WindowsAzure_Exception |
|
365 */ |
|
366 public function deleteEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false) |
|
367 { |
|
368 if ($tableName === '') { |
|
369 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); |
|
370 } |
|
371 if ($entity === null) { |
|
372 throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.'); |
|
373 } |
|
374 |
|
375 // Add header information |
|
376 $headers = array(); |
|
377 if (!$this->isInBatch()) { |
|
378 // http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/9e255447-4dc7-458a-99d3-bdc04bdc5474/ |
|
379 $headers['Content-Type'] = 'application/atom+xml'; |
|
380 } |
|
381 $headers['Content-Length'] = 0; |
|
382 if (!$verifyEtag) { |
|
383 $headers['If-Match'] = '*'; |
|
384 } else { |
|
385 $headers['If-Match'] = $entity->getEtag(); |
|
386 } |
|
387 |
|
388 // Perform request |
|
389 $response = null; |
|
390 if ($this->isInBatch()) { |
|
391 $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Zend_Http_Client::DELETE, $headers, true, null); |
|
392 return null; |
|
393 } else { |
|
394 $response = $this->_performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Zend_Http_Client::DELETE, $headers, true, null); |
|
395 } |
|
396 if (!$response->isSuccessful()) { |
|
397 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); |
|
398 } |
|
399 } |
|
400 |
|
401 /** |
|
402 * Retrieve entity from table, by id |
|
403 * |
|
404 * @param string $tableName Table name |
|
405 * @param string $partitionKey Partition key |
|
406 * @param string $rowKey Row key |
|
407 * @param string $entityClass Entity class name* |
|
408 * @return Zend_Service_WindowsAzure_Storage_TableEntity |
|
409 * @throws Zend_Service_WindowsAzure_Exception |
|
410 */ |
|
411 public function retrieveEntityById($tableName = '', $partitionKey = '', $rowKey = '', $entityClass = 'Zend_Service_WindowsAzure_Storage_DynamicTableEntity') |
|
412 { |
|
413 if ($tableName === '') { |
|
414 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); |
|
415 } |
|
416 if ($partitionKey === '') { |
|
417 throw new Zend_Service_WindowsAzure_Exception('Partition key is not specified.'); |
|
418 } |
|
419 if ($rowKey === '') { |
|
420 throw new Zend_Service_WindowsAzure_Exception('Row key is not specified.'); |
|
421 } |
|
422 if ($entityClass === '') { |
|
423 throw new Zend_Service_WindowsAzure_Exception('Entity class is not specified.'); |
|
424 } |
|
425 |
|
426 |
|
427 // Check for combined size of partition key and row key |
|
428 // http://msdn.microsoft.com/en-us/library/dd179421.aspx |
|
429 if (strlen($partitionKey . $rowKey) >= 256) { |
|
430 // Start a batch if possible |
|
431 if ($this->isInBatch()) { |
|
432 throw new Zend_Service_WindowsAzure_Exception('Entity cannot be retrieved. A transaction is required to retrieve the entity, but another transaction is already active.'); |
|
433 } |
|
434 |
|
435 $this->startBatch(); |
|
436 } |
|
437 |
|
438 // Fetch entities from Azure |
|
439 $result = $this->retrieveEntities( |
|
440 $this->select() |
|
441 ->from($tableName) |
|
442 ->wherePartitionKey($partitionKey) |
|
443 ->whereRowKey($rowKey), |
|
444 '', |
|
445 $entityClass |
|
446 ); |
|
447 |
|
448 // Return |
|
449 if (count($result) == 1) { |
|
450 return $result[0]; |
|
451 } |
|
452 |
|
453 return null; |
|
454 } |
|
455 |
|
456 /** |
|
457 * Create a new Zend_Service_WindowsAzure_Storage_TableEntityQuery |
|
458 * |
|
459 * @return Zend_Service_WindowsAzure_Storage_TableEntityQuery |
|
460 */ |
|
461 public function select() |
|
462 { |
|
463 return new Zend_Service_WindowsAzure_Storage_TableEntityQuery(); |
|
464 } |
|
465 |
|
466 /** |
|
467 * Retrieve entities from table |
|
468 * |
|
469 * @param string $tableName|Zend_Service_WindowsAzure_Storage_TableEntityQuery Table name -or- Zend_Service_WindowsAzure_Storage_TableEntityQuery instance |
|
470 * @param string $filter Filter condition (not applied when $tableName is a Zend_Service_WindowsAzure_Storage_TableEntityQuery instance) |
|
471 * @param string $entityClass Entity class name |
|
472 * @param string $nextPartitionKey Next partition key, used for listing entities when total amount of entities is > 1000. |
|
473 * @param string $nextRowKey Next row key, used for listing entities when total amount of entities is > 1000. |
|
474 * @return array Array of Zend_Service_WindowsAzure_Storage_TableEntity |
|
475 * @throws Zend_Service_WindowsAzure_Exception |
|
476 */ |
|
477 public function retrieveEntities($tableName = '', $filter = '', $entityClass = 'Zend_Service_WindowsAzure_Storage_DynamicTableEntity', $nextPartitionKey = null, $nextRowKey = null) |
|
478 { |
|
479 if ($tableName === '') { |
|
480 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); |
|
481 } |
|
482 if ($entityClass === '') { |
|
483 throw new Zend_Service_WindowsAzure_Exception('Entity class is not specified.'); |
|
484 } |
|
485 |
|
486 // Convenience... |
|
487 if (class_exists($filter)) { |
|
488 $entityClass = $filter; |
|
489 $filter = ''; |
|
490 } |
|
491 |
|
492 // Query string |
|
493 $queryString = ''; |
|
494 |
|
495 // Determine query |
|
496 if (is_string($tableName)) { |
|
497 // Option 1: $tableName is a string |
|
498 |
|
499 // Append parentheses |
|
500 $tableName .= '()'; |
|
501 |
|
502 // Build query |
|
503 $query = array(); |
|
504 |
|
505 // Filter? |
|
506 if ($filter !== '') { |
|
507 $query[] = '$filter=' . Zend_Service_WindowsAzure_Storage_TableEntityQuery::encodeQuery($filter); |
|
508 } |
|
509 |
|
510 // Build queryString |
|
511 if (count($query) > 0) { |
|
512 $queryString = '?' . implode('&', $query); |
|
513 } |
|
514 } else if (get_class($tableName) == 'Zend_Service_WindowsAzure_Storage_TableEntityQuery') { |
|
515 // Option 2: $tableName is a Zend_Service_WindowsAzure_Storage_TableEntityQuery instance |
|
516 |
|
517 // Build queryString |
|
518 $queryString = $tableName->assembleQueryString(true); |
|
519 |
|
520 // Change $tableName |
|
521 $tableName = $tableName->assembleFrom(true); |
|
522 } else { |
|
523 throw new Zend_Service_WindowsAzure_Exception('Invalid argument: $tableName'); |
|
524 } |
|
525 |
|
526 // Add continuation querystring parameters? |
|
527 if ($nextPartitionKey !== null && $nextRowKey !== null) { |
|
528 if ($queryString !== '') { |
|
529 $queryString .= '&'; |
|
530 } |
|
531 |
|
532 $queryString .= '&NextPartitionKey=' . rawurlencode($nextPartitionKey) . '&NextRowKey=' . rawurlencode($nextRowKey); |
|
533 } |
|
534 |
|
535 // Perform request |
|
536 $response = null; |
|
537 if ($this->isInBatch() && $this->getCurrentBatch()->getOperationCount() == 0) { |
|
538 $this->getCurrentBatch()->enlistOperation($tableName, $queryString, Zend_Http_Client::GET, array(), true, null); |
|
539 $response = $this->getCurrentBatch()->commit(); |
|
540 |
|
541 // Get inner response (multipart) |
|
542 $innerResponse = $response->getBody(); |
|
543 $innerResponse = substr($innerResponse, strpos($innerResponse, 'HTTP/1.1 200 OK')); |
|
544 $innerResponse = substr($innerResponse, 0, strpos($innerResponse, '--batchresponse')); |
|
545 $response = Zend_Http_Response::fromString($innerResponse); |
|
546 } else { |
|
547 $response = $this->_performRequest($tableName, $queryString, Zend_Http_Client::GET, array(), true, null); |
|
548 } |
|
549 |
|
550 if ($response->isSuccessful()) { |
|
551 // Parse result |
|
552 $result = $this->_parseResponse($response); |
|
553 if (!$result) { |
|
554 return array(); |
|
555 } |
|
556 |
|
557 $entries = null; |
|
558 if ($result->entry) { |
|
559 if (count($result->entry) > 1) { |
|
560 $entries = $result->entry; |
|
561 } else { |
|
562 $entries = array($result->entry); |
|
563 } |
|
564 } else { |
|
565 // This one is tricky... If we have properties defined, we have an entity. |
|
566 $properties = $result->xpath('//m:properties'); |
|
567 if ($properties) { |
|
568 $entries = array($result); |
|
569 } else { |
|
570 return array(); |
|
571 } |
|
572 } |
|
573 |
|
574 // Create return value |
|
575 $returnValue = array(); |
|
576 foreach ($entries as $entry) { |
|
577 // Parse properties |
|
578 $properties = $entry->xpath('.//m:properties'); |
|
579 $properties = $properties[0]->children('http://schemas.microsoft.com/ado/2007/08/dataservices'); |
|
580 |
|
581 // Create entity |
|
582 $entity = new $entityClass('', ''); |
|
583 $entity->setAzureValues((array)$properties, true); |
|
584 |
|
585 // If we have a Zend_Service_WindowsAzure_Storage_DynamicTableEntity, make sure all property types are OK |
|
586 if ($entity instanceof Zend_Service_WindowsAzure_Storage_DynamicTableEntity) { |
|
587 foreach ($properties as $key => $value) { |
|
588 $attributes = $value->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'); |
|
589 $type = (string)$attributes['type']; |
|
590 if ($type !== '') { |
|
591 $entity->setAzurePropertyType($key, $type); |
|
592 } |
|
593 } |
|
594 } |
|
595 |
|
596 // Update etag |
|
597 $etag = $entry->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'); |
|
598 $etag = (string)$etag['etag']; |
|
599 $entity->setEtag($etag); |
|
600 |
|
601 // Add to result |
|
602 $returnValue[] = $entity; |
|
603 } |
|
604 |
|
605 // More entities? |
|
606 if ($response->getHeader('x-ms-continuation-NextPartitionKey') !== null && $response->getHeader('x-ms-continuation-NextRowKey') !== null) { |
|
607 if (strpos($queryString, '$top') === false) { |
|
608 $returnValue = array_merge($returnValue, $this->retrieveEntities($tableName, $filter, $entityClass, $response->getHeader('x-ms-continuation-NextPartitionKey'), $response->getHeader('x-ms-continuation-NextRowKey'))); |
|
609 } |
|
610 } |
|
611 |
|
612 // Return |
|
613 return $returnValue; |
|
614 } else { |
|
615 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); |
|
616 } |
|
617 } |
|
618 |
|
619 /** |
|
620 * Update entity by replacing it |
|
621 * |
|
622 * @param string $tableName Table name |
|
623 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update |
|
624 * @param boolean $verifyEtag Verify etag of the entity (used for concurrency) |
|
625 * @throws Zend_Service_WindowsAzure_Exception |
|
626 */ |
|
627 public function updateEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false) |
|
628 { |
|
629 return $this->_changeEntity(Zend_Http_Client::PUT, $tableName, $entity, $verifyEtag); |
|
630 } |
|
631 |
|
632 /** |
|
633 * Update entity by adding or updating properties |
|
634 * |
|
635 * @param string $tableName Table name |
|
636 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update |
|
637 * @param boolean $verifyEtag Verify etag of the entity (used for concurrency) |
|
638 * @param array $properties Properties to merge. All properties will be used when omitted. |
|
639 * @throws Zend_Service_WindowsAzure_Exception |
|
640 */ |
|
641 public function mergeEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false, $properties = array()) |
|
642 { |
|
643 $mergeEntity = null; |
|
644 if (is_array($properties) && count($properties) > 0) { |
|
645 // Build a new object |
|
646 $mergeEntity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($entity->getPartitionKey(), $entity->getRowKey()); |
|
647 |
|
648 // Keep only values mentioned in $properties |
|
649 $azureValues = $entity->getAzureValues(); |
|
650 foreach ($azureValues as $key => $value) { |
|
651 if (in_array($value->Name, $properties)) { |
|
652 $mergeEntity->setAzureProperty($value->Name, $value->Value, $value->Type); |
|
653 } |
|
654 } |
|
655 } else { |
|
656 $mergeEntity = $entity; |
|
657 } |
|
658 |
|
659 // Ensure entity timestamp matches updated timestamp |
|
660 $entity->setTimestamp($this->isoDate()); |
|
661 |
|
662 return $this->_changeEntity(Zend_Http_Client::MERGE, $tableName, $mergeEntity, $verifyEtag); |
|
663 } |
|
664 |
|
665 /** |
|
666 * Get error message from Zend_Http_Response |
|
667 * |
|
668 * @param Zend_Http_Response $response Repsonse |
|
669 * @param string $alternativeError Alternative error message |
|
670 * @return string |
|
671 */ |
|
672 protected function _getErrorMessage(Zend_Http_Response $response, $alternativeError = 'Unknown error.') |
|
673 { |
|
674 $response = $this->_parseResponse($response); |
|
675 if ($response && $response->message) { |
|
676 return (string)$response->message; |
|
677 } else { |
|
678 return $alternativeError; |
|
679 } |
|
680 } |
|
681 |
|
682 /** |
|
683 * Update entity / merge entity |
|
684 * |
|
685 * @param string $httpVerb HTTP verb to use (PUT = update, MERGE = merge) |
|
686 * @param string $tableName Table name |
|
687 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update |
|
688 * @param boolean $verifyEtag Verify etag of the entity (used for concurrency) |
|
689 * @throws Zend_Service_WindowsAzure_Exception |
|
690 */ |
|
691 protected function _changeEntity($httpVerb = Zend_Http_Client::PUT, $tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false) |
|
692 { |
|
693 if ($tableName === '') { |
|
694 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); |
|
695 } |
|
696 if ($entity === null) { |
|
697 throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.'); |
|
698 } |
|
699 |
|
700 // Add header information |
|
701 $headers = array(); |
|
702 $headers['Content-Type'] = 'application/atom+xml'; |
|
703 $headers['Content-Length'] = 0; |
|
704 if (!$verifyEtag) { |
|
705 $headers['If-Match'] = '*'; |
|
706 } else { |
|
707 $headers['If-Match'] = $entity->getEtag(); |
|
708 } |
|
709 |
|
710 // Generate request body |
|
711 $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?> |
|
712 <entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom"> |
|
713 <title /> |
|
714 <updated>{tpl:Updated}</updated> |
|
715 <author> |
|
716 <name /> |
|
717 </author> |
|
718 <id /> |
|
719 <content type="application/xml"> |
|
720 <m:properties> |
|
721 {tpl:Properties} |
|
722 </m:properties> |
|
723 </content> |
|
724 </entry>'; |
|
725 |
|
726 // Attempt to get timestamp from entity |
|
727 $timestamp = $entity->getTimestamp(); |
|
728 if ($timestamp == Zend_Service_WindowsAzure_Storage_TableEntity::DEFAULT_TIMESTAMP) { |
|
729 $timestamp = $this->isoDate(); |
|
730 } |
|
731 |
|
732 $requestBody = $this->_fillTemplate($requestBody, array( |
|
733 'Updated' => $timestamp, |
|
734 'Properties' => $this->_generateAzureRepresentation($entity) |
|
735 )); |
|
736 |
|
737 // Add header information |
|
738 $headers = array(); |
|
739 $headers['Content-Type'] = 'application/atom+xml'; |
|
740 if (!$verifyEtag) { |
|
741 $headers['If-Match'] = '*'; |
|
742 } else { |
|
743 $headers['If-Match'] = $entity->getEtag(); |
|
744 } |
|
745 |
|
746 // Perform request |
|
747 $response = null; |
|
748 if ($this->isInBatch()) { |
|
749 $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\',RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody); |
|
750 return null; |
|
751 } else { |
|
752 $response = $this->_performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\',RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody); |
|
753 } |
|
754 if ($response->isSuccessful()) { |
|
755 // Update properties |
|
756 $entity->setEtag($response->getHeader('Etag')); |
|
757 $entity->setTimestamp($response->getHeader('Last-modified')); |
|
758 |
|
759 return $entity; |
|
760 } else { |
|
761 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); |
|
762 } |
|
763 } |
|
764 |
|
765 /** |
|
766 * Generate RFC 1123 compliant date string |
|
767 * |
|
768 * @return string |
|
769 */ |
|
770 protected function _rfcDate() |
|
771 { |
|
772 return gmdate('D, d M Y H:i:s', time()) . ' GMT'; // RFC 1123 |
|
773 } |
|
774 |
|
775 /** |
|
776 * Fill text template with variables from key/value array |
|
777 * |
|
778 * @param string $templateText Template text |
|
779 * @param array $variables Array containing key/value pairs |
|
780 * @return string |
|
781 */ |
|
782 protected function _fillTemplate($templateText, $variables = array()) |
|
783 { |
|
784 foreach ($variables as $key => $value) { |
|
785 $templateText = str_replace('{tpl:' . $key . '}', $value, $templateText); |
|
786 } |
|
787 return $templateText; |
|
788 } |
|
789 |
|
790 /** |
|
791 * Generate Azure representation from entity (creates atompub markup from properties) |
|
792 * |
|
793 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity |
|
794 * @return string |
|
795 */ |
|
796 protected function _generateAzureRepresentation(Zend_Service_WindowsAzure_Storage_TableEntity $entity = null) |
|
797 { |
|
798 // Generate Azure representation from entity |
|
799 $azureRepresentation = array(); |
|
800 $azureValues = $entity->getAzureValues(); |
|
801 foreach ($azureValues as $azureValue) { |
|
802 $value = array(); |
|
803 $value[] = '<d:' . $azureValue->Name; |
|
804 if ($azureValue->Type != '') { |
|
805 $value[] = ' m:type="' . $azureValue->Type . '"'; |
|
806 } |
|
807 if ($azureValue->Value === null) { |
|
808 $value[] = ' m:null="true"'; |
|
809 } |
|
810 $value[] = '>'; |
|
811 |
|
812 if ($azureValue->Value !== null) { |
|
813 if (strtolower($azureValue->Type) == 'edm.boolean') { |
|
814 $value[] = ($azureValue->Value == true ? '1' : '0'); |
|
815 } else { |
|
816 $value[] = htmlspecialchars($azureValue->Value); |
|
817 } |
|
818 } |
|
819 |
|
820 $value[] = '</d:' . $azureValue->Name . '>'; |
|
821 $azureRepresentation[] = implode('', $value); |
|
822 } |
|
823 |
|
824 return implode('', $azureRepresentation); |
|
825 } |
|
826 |
|
827 /** |
|
828 * Perform request using Zend_Http_Client channel |
|
829 * |
|
830 * @param string $path Path |
|
831 * @param string $queryString Query string |
|
832 * @param string $httpVerb HTTP verb the request will use |
|
833 * @param array $headers x-ms headers to add |
|
834 * @param boolean $forTableStorage Is the request for table storage? |
|
835 * @param mixed $rawData Optional RAW HTTP data to be sent over the wire |
|
836 * @param string $resourceType Resource type |
|
837 * @param string $requiredPermission Required permission |
|
838 * @return Zend_Http_Response |
|
839 */ |
|
840 protected function _performRequest( |
|
841 $path = '/', |
|
842 $queryString = '', |
|
843 $httpVerb = Zend_Http_Client::GET, |
|
844 $headers = array(), |
|
845 $forTableStorage = false, |
|
846 $rawData = null, |
|
847 $resourceType = Zend_Service_WindowsAzure_Storage::RESOURCE_UNKNOWN, |
|
848 $requiredPermission = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::PERMISSION_READ |
|
849 ) { |
|
850 // Add headers |
|
851 $headers['DataServiceVersion'] = '1.0;NetFx'; |
|
852 $headers['MaxDataServiceVersion'] = '1.0;NetFx'; |
|
853 |
|
854 // Perform request |
|
855 return parent::_performRequest( |
|
856 $path, |
|
857 $queryString, |
|
858 $httpVerb, |
|
859 $headers, |
|
860 $forTableStorage, |
|
861 $rawData, |
|
862 $resourceType, |
|
863 $requiredPermission |
|
864 ); |
|
865 } |
|
866 } |