|
1 <?php |
|
2 /** |
|
3 * LICENSE |
|
4 * |
|
5 * This source file is subject to the new BSD license that is bundled |
|
6 * with this package in the file LICENSE.txt. |
|
7 * It is also available through the world-wide-web at this URL: |
|
8 * http://framework.zend.com/license/new-bsd |
|
9 * If you did not receive a copy of the license and are unable to |
|
10 * obtain it through the world-wide-web, please send an email |
|
11 * to license@zend.com so we can send you a copy immediately. |
|
12 * |
|
13 * @category Zend |
|
14 * @package Zend_Cloud |
|
15 * @subpackage DocumentService |
|
16 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
17 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
18 */ |
|
19 |
|
20 require_once 'Zend/Cloud/DocumentService/Adapter/AbstractAdapter.php'; |
|
21 require_once 'Zend/Cloud/DocumentService/Adapter/SimpleDb/Query.php'; |
|
22 require_once 'Zend/Cloud/DocumentService/Exception.php'; |
|
23 require_once 'Zend/Service/Amazon/SimpleDb.php'; |
|
24 require_once 'Zend/Service/Amazon/SimpleDb/Attribute.php'; |
|
25 |
|
26 /** |
|
27 * SimpleDB adapter for document service. |
|
28 * |
|
29 * @category Zend |
|
30 * @package Zend_Cloud |
|
31 * @subpackage DocumentService |
|
32 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
33 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
34 */ |
|
35 class Zend_Cloud_DocumentService_Adapter_SimpleDb |
|
36 extends Zend_Cloud_DocumentService_Adapter_AbstractAdapter |
|
37 { |
|
38 /* |
|
39 * Options array keys for the SimpleDB adapter. |
|
40 */ |
|
41 const AWS_ACCESS_KEY = 'aws_accesskey'; |
|
42 const AWS_SECRET_KEY = 'aws_secretkey'; |
|
43 |
|
44 const ITEM_NAME = 'ItemName'; |
|
45 |
|
46 const MERGE_OPTION = "merge"; |
|
47 const RETURN_DOCUMENTS = "return_documents"; |
|
48 |
|
49 const DEFAULT_QUERY_CLASS = 'Zend_Cloud_DocumentService_Adapter_SimpleDb_Query'; |
|
50 |
|
51 |
|
52 /** |
|
53 * SQS service instance. |
|
54 * @var Zend_Service_Amazon_SimpleDb |
|
55 */ |
|
56 protected $_simpleDb; |
|
57 |
|
58 /** |
|
59 * Class to utilize for new query objects |
|
60 * @var string |
|
61 */ |
|
62 protected $_queryClass = 'Zend_Cloud_DocumentService_Adapter_SimpleDb_Query'; |
|
63 |
|
64 /** |
|
65 * Constructor |
|
66 * |
|
67 * @param array|Zend_Config $options |
|
68 * @return void |
|
69 */ |
|
70 public function __construct($options = array()) |
|
71 { |
|
72 if ($options instanceof Zend_Config) { |
|
73 $options = $options->toArray(); |
|
74 } |
|
75 |
|
76 if (!is_array($options)) { |
|
77 throw new Zend_Cloud_DocumentService_Exception('Invalid options provided to constructor'); |
|
78 } |
|
79 |
|
80 $this->_simpleDb = new Zend_Service_Amazon_SimpleDb( |
|
81 $options[self::AWS_ACCESS_KEY], $options[self::AWS_SECRET_KEY] |
|
82 ); |
|
83 |
|
84 if (isset($options[self::HTTP_ADAPTER])) { |
|
85 $this->_sqs->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]); |
|
86 } |
|
87 |
|
88 if (isset($options[self::DOCUMENT_CLASS])) { |
|
89 $this->setDocumentClass($options[self::DOCUMENT_CLASS]); |
|
90 } |
|
91 |
|
92 if (isset($options[self::DOCUMENTSET_CLASS])) { |
|
93 $this->setDocumentSetClass($options[self::DOCUMENTSET_CLASS]); |
|
94 } |
|
95 |
|
96 if (isset($options[self::QUERY_CLASS])) { |
|
97 $this->setQueryClass($options[self::QUERY_CLASS]); |
|
98 } |
|
99 } |
|
100 |
|
101 /** |
|
102 * Create collection. |
|
103 * |
|
104 * @param string $name |
|
105 * @param array $options |
|
106 * @return void |
|
107 */ |
|
108 public function createCollection($name, $options = null) |
|
109 { |
|
110 try { |
|
111 $this->_simpleDb->createDomain($name); |
|
112 } catch(Zend_Service_Amazon_Exception $e) { |
|
113 throw new Zend_Cloud_DocumentService_Exception('Error on domain creation: '.$e->getMessage(), $e->getCode(), $e); |
|
114 } |
|
115 } |
|
116 |
|
117 /** |
|
118 * Delete collection. |
|
119 * |
|
120 * @param string $name |
|
121 * @param array $options |
|
122 * @return void |
|
123 */ |
|
124 public function deleteCollection($name, $options = null) |
|
125 { |
|
126 try { |
|
127 $this->_simpleDb->deleteDomain($name); |
|
128 } catch(Zend_Service_Amazon_Exception $e) { |
|
129 throw new Zend_Cloud_DocumentService_Exception('Error on collection deletion: '.$e->getMessage(), $e->getCode(), $e); |
|
130 } |
|
131 } |
|
132 |
|
133 /** |
|
134 * List collections. |
|
135 * |
|
136 * @param array $options |
|
137 * @return array |
|
138 */ |
|
139 public function listCollections($options = null) |
|
140 { |
|
141 try { |
|
142 // TODO package this in Pages |
|
143 $domains = $this->_simpleDb->listDomains()->getData(); |
|
144 } catch(Zend_Service_Amazon_Exception $e) { |
|
145 throw new Zend_Cloud_DocumentService_Exception('Error on collection deletion: '.$e->getMessage(), $e->getCode(), $e); |
|
146 } |
|
147 |
|
148 return $domains; |
|
149 } |
|
150 |
|
151 /** |
|
152 * List documents |
|
153 * |
|
154 * Returns a key/value array of document names to document objects. |
|
155 * |
|
156 * @param string $collectionName Name of collection for which to list documents |
|
157 * @param array|null $options |
|
158 * @return Zend_Cloud_DocumentService_DocumentSet |
|
159 */ |
|
160 public function listDocuments($collectionName, array $options = null) |
|
161 { |
|
162 $query = $this->select('*')->from($collectionName); |
|
163 $items = $this->query($collectionName, $query, $options); |
|
164 return $items; |
|
165 } |
|
166 |
|
167 /** |
|
168 * Insert document |
|
169 * |
|
170 * @param string $collectionName Collection into which to insert document |
|
171 * @param array|Zend_Cloud_DocumentService_Document $document |
|
172 * @param array $options |
|
173 * @return void |
|
174 */ |
|
175 public function insertDocument($collectionName, $document, $options = null) |
|
176 { |
|
177 if (is_array($document)) { |
|
178 $document = $this->_getDocumentFromArray($document); |
|
179 } |
|
180 |
|
181 if (!$document instanceof Zend_Cloud_DocumentService_Document) { |
|
182 throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); |
|
183 } |
|
184 |
|
185 try { |
|
186 $this->_simpleDb->putAttributes( |
|
187 $collectionName, |
|
188 $document->getID(), |
|
189 $this->_makeAttributes($document->getID(), $document->getFields()) |
|
190 ); |
|
191 } catch(Zend_Service_Amazon_Exception $e) { |
|
192 throw new Zend_Cloud_DocumentService_Exception('Error on document insertion: '.$e->getMessage(), $e->getCode(), $e); |
|
193 } |
|
194 } |
|
195 |
|
196 /** |
|
197 * Replace an existing document with a new version |
|
198 * |
|
199 * @param string $collectionName |
|
200 * @param array|Zend_Cloud_DocumentService_Document $document |
|
201 * @param array $options |
|
202 * @return void |
|
203 */ |
|
204 public function replaceDocument($collectionName, $document, $options = null) |
|
205 { |
|
206 if (is_array($document)) { |
|
207 $document = $this->_getDocumentFromArray($document); |
|
208 } |
|
209 |
|
210 if (!$document instanceof Zend_Cloud_DocumentService_Document) { |
|
211 throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); |
|
212 } |
|
213 |
|
214 // Delete document first, then insert. PutAttributes always keeps any |
|
215 // fields not referenced in the payload, but present in the document |
|
216 $documentId = $document->getId(); |
|
217 $fields = $document->getFields(); |
|
218 $docClass = get_class($document); |
|
219 $this->deleteDocument($collectionName, $document, $options); |
|
220 |
|
221 $document = new $docClass($fields, $documentId); |
|
222 $this->insertDocument($collectionName, $document); |
|
223 } |
|
224 |
|
225 /** |
|
226 * Update document. The new document replaces the existing document. |
|
227 * |
|
228 * Option 'merge' specifies to add all attributes (if true) or |
|
229 * specific attributes ("attr" => true) instead of replacing them. |
|
230 * By default, attributes are replaced. |
|
231 * |
|
232 * @param string $collectionName |
|
233 * @param mixed|Zend_Cloud_DocumentService_Document $documentId Document ID, adapter-dependent |
|
234 * @param array|Zend_Cloud_DocumentService_Document $fieldset Set of fields to update |
|
235 * @param array $options |
|
236 * @return boolean |
|
237 */ |
|
238 public function updateDocument($collectionName, $documentId, $fieldset = null, $options = null) |
|
239 { |
|
240 if (null === $fieldset && $documentId instanceof Zend_Cloud_DocumentService_Document) { |
|
241 $fieldset = $documentId->getFields(); |
|
242 if (empty($documentId)) { |
|
243 $documentId = $documentId->getId(); |
|
244 } |
|
245 } elseif ($fieldset instanceof Zend_Cloud_DocumentService_Document) { |
|
246 if (empty($documentId)) { |
|
247 $documentId = $fieldset->getId(); |
|
248 } |
|
249 $fieldset = $fieldset->getFields(); |
|
250 } |
|
251 |
|
252 $replace = array(); |
|
253 if (empty($options[self::MERGE_OPTION])) { |
|
254 // no merge option - we replace all |
|
255 foreach ($fieldset as $key => $value) { |
|
256 $replace[$key] = true; |
|
257 } |
|
258 } elseif (is_array($options[self::MERGE_OPTION])) { |
|
259 foreach ($fieldset as $key => $value) { |
|
260 if (empty($options[self::MERGE_OPTION][$key])) { |
|
261 // if there's merge key, we add it, otherwise we replace it |
|
262 $replace[$key] = true; |
|
263 } |
|
264 } |
|
265 } // otherwise $replace is empty - all is merged |
|
266 |
|
267 try { |
|
268 $this->_simpleDb->putAttributes( |
|
269 $collectionName, |
|
270 $documentId, |
|
271 $this->_makeAttributes($documentId, $fieldset), |
|
272 $replace |
|
273 ); |
|
274 } catch(Zend_Service_Amazon_Exception $e) { |
|
275 throw new Zend_Cloud_DocumentService_Exception('Error on document update: '.$e->getMessage(), $e->getCode(), $e); |
|
276 } |
|
277 return true; |
|
278 } |
|
279 |
|
280 /** |
|
281 * Delete document. |
|
282 * |
|
283 * @param string $collectionName Collection from which to delete document |
|
284 * @param mixed $document Document ID or Document object. |
|
285 * @param array $options |
|
286 * @return boolean |
|
287 */ |
|
288 public function deleteDocument($collectionName, $document, $options = null) |
|
289 { |
|
290 if ($document instanceof Zend_Cloud_DocumentService_Document) { |
|
291 $document = $document->getId(); |
|
292 } |
|
293 try { |
|
294 $this->_simpleDb->deleteAttributes($collectionName, $document); |
|
295 } catch(Zend_Service_Amazon_Exception $e) { |
|
296 throw new Zend_Cloud_DocumentService_Exception('Error on document deletion: '.$e->getMessage(), $e->getCode(), $e); |
|
297 } |
|
298 return true; |
|
299 } |
|
300 |
|
301 /** |
|
302 * Fetch single document by ID |
|
303 * |
|
304 * @param string $collectionName Collection name |
|
305 * @param mixed $documentId Document ID, adapter-dependent |
|
306 * @param array $options |
|
307 * @return Zend_Cloud_DocumentService_Document |
|
308 */ |
|
309 public function fetchDocument($collectionName, $documentId, $options = null) |
|
310 { |
|
311 try { |
|
312 $attributes = $this->_simpleDb->getAttributes($collectionName, $documentId); |
|
313 if ($attributes == false || count($attributes) == 0) { |
|
314 return false; |
|
315 } |
|
316 return $this->_resolveAttributes($attributes, true); |
|
317 } catch(Zend_Service_Amazon_Exception $e) { |
|
318 throw new Zend_Cloud_DocumentService_Exception('Error on fetching document: '.$e->getMessage(), $e->getCode(), $e); |
|
319 } |
|
320 } |
|
321 |
|
322 /** |
|
323 * Query for documents stored in the document service. If a string is passed in |
|
324 * $query, the query string will be passed directly to the service. |
|
325 * |
|
326 * @param string $collectionName Collection name |
|
327 * @param string $query |
|
328 * @param array $options |
|
329 * @return array Zend_Cloud_DocumentService_DocumentSet |
|
330 */ |
|
331 public function query($collectionName, $query, $options = null) |
|
332 { |
|
333 $returnDocs = isset($options[self::RETURN_DOCUMENTS]) |
|
334 ? (bool) $options[self::RETURN_DOCUMENTS] |
|
335 : true; |
|
336 |
|
337 try { |
|
338 if ($query instanceof Zend_Cloud_DocumentService_Adapter_SimpleDb_Query) { |
|
339 $query = $query->assemble($collectionName); |
|
340 } |
|
341 $result = $this->_simpleDb->select($query); |
|
342 } catch(Zend_Service_Amazon_Exception $e) { |
|
343 throw new Zend_Cloud_DocumentService_Exception('Error on document query: '.$e->getMessage(), $e->getCode(), $e); |
|
344 } |
|
345 |
|
346 return $this->_getDocumentSetFromResultSet($result, $returnDocs); |
|
347 } |
|
348 |
|
349 /** |
|
350 * Create query statement |
|
351 * |
|
352 * @param string $fields |
|
353 * @return Zend_Cloud_DocumentService_Adapter_SimpleDb_Query |
|
354 */ |
|
355 public function select($fields = null) |
|
356 { |
|
357 $queryClass = $this->getQueryClass(); |
|
358 if (!class_exists($queryClass)) { |
|
359 require_once 'Zend/Loader.php'; |
|
360 Zend_Loader::loadClass($queryClass); |
|
361 } |
|
362 |
|
363 $query = new $queryClass($this); |
|
364 $defaultClass = self::DEFAULT_QUERY_CLASS; |
|
365 if (!$query instanceof $defaultClass) { |
|
366 throw new Zend_Cloud_DocumentService_Exception('Query class must extend ' . self::DEFAULT_QUERY_CLASS); |
|
367 } |
|
368 |
|
369 $query->select($fields); |
|
370 return $query; |
|
371 } |
|
372 |
|
373 /** |
|
374 * Get the concrete service client |
|
375 * |
|
376 * @return Zend_Service_Amazon_SimpleDb |
|
377 */ |
|
378 public function getClient() |
|
379 { |
|
380 return $this->_simpleDb; |
|
381 } |
|
382 |
|
383 /** |
|
384 * Convert array of key-value pairs to array of Amazon attributes |
|
385 * |
|
386 * @param string $name |
|
387 * @param array $attributes |
|
388 * @return array |
|
389 */ |
|
390 protected function _makeAttributes($name, $attributes) |
|
391 { |
|
392 $result = array(); |
|
393 foreach ($attributes as $key => $attr) { |
|
394 $result[] = new Zend_Service_Amazon_SimpleDb_Attribute($name, $key, $attr); |
|
395 } |
|
396 return $result; |
|
397 } |
|
398 |
|
399 /** |
|
400 * Convert array of Amazon attributes to array of key-value pairs |
|
401 * |
|
402 * @param array $attributes |
|
403 * @return array |
|
404 */ |
|
405 protected function _resolveAttributes($attributes, $returnDocument = false) |
|
406 { |
|
407 $result = array(); |
|
408 foreach ($attributes as $attr) { |
|
409 $value = $attr->getValues(); |
|
410 if (count($value) == 0) { |
|
411 $value = null; |
|
412 } elseif (count($value) == 1) { |
|
413 $value = $value[0]; |
|
414 } |
|
415 $result[$attr->getName()] = $value; |
|
416 } |
|
417 |
|
418 // Return as document object? |
|
419 if ($returnDocument) { |
|
420 $documentClass = $this->getDocumentClass(); |
|
421 return new $documentClass($result, $attr->getItemName()); |
|
422 } |
|
423 |
|
424 return $result; |
|
425 } |
|
426 |
|
427 /** |
|
428 * Create suitable document from array of fields |
|
429 * |
|
430 * @param array $document |
|
431 * @return Zend_Cloud_DocumentService_Document |
|
432 */ |
|
433 protected function _getDocumentFromArray($document) |
|
434 { |
|
435 if (!isset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD])) { |
|
436 if (isset($document[self::ITEM_NAME])) { |
|
437 $key = $document[self::ITEM_NAME]; |
|
438 unset($document[self::ITEM_NAME]); |
|
439 } else { |
|
440 throw new Zend_Cloud_DocumentService_Exception('Fields array should contain the key field '.Zend_Cloud_DocumentService_Document::KEY_FIELD); |
|
441 } |
|
442 } else { |
|
443 $key = $document[Zend_Cloud_DocumentService_Document::KEY_FIELD]; |
|
444 unset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD]); |
|
445 } |
|
446 |
|
447 $documentClass = $this->getDocumentClass(); |
|
448 return new $documentClass($document, $key); |
|
449 } |
|
450 |
|
451 /** |
|
452 * Create a DocumentSet from a SimpleDb resultset |
|
453 * |
|
454 * @param Zend_Service_Amazon_SimpleDb_Page $resultSet |
|
455 * @param bool $returnDocs |
|
456 * @return Zend_Cloud_DocumentService_DocumentSet |
|
457 */ |
|
458 protected function _getDocumentSetFromResultSet(Zend_Service_Amazon_SimpleDb_Page $resultSet, $returnDocs = true) |
|
459 { |
|
460 $docs = array(); |
|
461 foreach ($resultSet->getData() as $item) { |
|
462 $docs[] = $this->_resolveAttributes($item, $returnDocs); |
|
463 } |
|
464 |
|
465 $setClass = $this->getDocumentSetClass(); |
|
466 return new $setClass($docs); |
|
467 } |
|
468 } |