0baa3f2531c5bc271f68c1a1063618b07bdf2a10
[yaffs-website] / vendor / doctrine / cache / lib / Doctrine / Common / Cache / RiakCache.php
1 <?php
2 /*
3  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14  *
15  * This software consists of voluntary contributions made by many individuals
16  * and is licensed under the MIT license. For more information, see
17  * <http://www.doctrine-project.org>.
18  */
19
20 namespace Doctrine\Common\Cache;
21
22 use Riak\Bucket;
23 use Riak\Connection;
24 use Riak\Input;
25 use Riak\Exception;
26 use Riak\Object;
27
28 /**
29  * Riak cache provider.
30  *
31  * @link   www.doctrine-project.org
32  * @since  1.1
33  * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
34  */
35 class RiakCache extends CacheProvider
36 {
37     const EXPIRES_HEADER = 'X-Riak-Meta-Expires';
38
39     /**
40      * @var \Riak\Bucket
41      */
42     private $bucket;
43
44     /**
45      * Sets the riak bucket instance to use.
46      *
47      * @param \Riak\Bucket $bucket
48      */
49     public function __construct(Bucket $bucket)
50     {
51         $this->bucket = $bucket;
52     }
53
54     /**
55      * {@inheritdoc}
56      */
57     protected function doFetch($id)
58     {
59         try {
60             $response = $this->bucket->get($id);
61
62             // No objects found
63             if ( ! $response->hasObject()) {
64                 return false;
65             }
66
67             // Check for attempted siblings
68             $object = ($response->hasSiblings())
69                 ? $this->resolveConflict($id, $response->getVClock(), $response->getObjectList())
70                 : $response->getFirstObject();
71
72             // Check for expired object
73             if ($this->isExpired($object)) {
74                 $this->bucket->delete($object);
75
76                 return false;
77             }
78
79             return unserialize($object->getContent());
80         } catch (Exception\RiakException $e) {
81             // Covers:
82             // - Riak\ConnectionException
83             // - Riak\CommunicationException
84             // - Riak\UnexpectedResponseException
85             // - Riak\NotFoundException
86         }
87
88         return false;
89     }
90
91     /**
92      * {@inheritdoc}
93      */
94     protected function doContains($id)
95     {
96         try {
97             // We only need the HEAD, not the entire object
98             $input = new Input\GetInput();
99
100             $input->setReturnHead(true);
101
102             $response = $this->bucket->get($id, $input);
103
104             // No objects found
105             if ( ! $response->hasObject()) {
106                 return false;
107             }
108
109             $object = $response->getFirstObject();
110
111             // Check for expired object
112             if ($this->isExpired($object)) {
113                 $this->bucket->delete($object);
114
115                 return false;
116             }
117
118             return true;
119         } catch (Exception\RiakException $e) {
120             // Do nothing
121         }
122
123         return false;
124     }
125
126     /**
127      * {@inheritdoc}
128      */
129     protected function doSave($id, $data, $lifeTime = 0)
130     {
131         try {
132             $object = new Object($id);
133
134             $object->setContent(serialize($data));
135
136             if ($lifeTime > 0) {
137                 $object->addMetadata(self::EXPIRES_HEADER, (string) (time() + $lifeTime));
138             }
139
140             $this->bucket->put($object);
141
142             return true;
143         } catch (Exception\RiakException $e) {
144             // Do nothing
145         }
146
147         return false;
148     }
149
150     /**
151      * {@inheritdoc}
152      */
153     protected function doDelete($id)
154     {
155         try {
156             $this->bucket->delete($id);
157
158             return true;
159         } catch (Exception\BadArgumentsException $e) {
160             // Key did not exist on cluster already
161         } catch (Exception\RiakException $e) {
162             // Covers:
163             // - Riak\Exception\ConnectionException
164             // - Riak\Exception\CommunicationException
165             // - Riak\Exception\UnexpectedResponseException
166         }
167
168         return false;
169     }
170
171     /**
172      * {@inheritdoc}
173      */
174     protected function doFlush()
175     {
176         try {
177             $keyList = $this->bucket->getKeyList();
178
179             foreach ($keyList as $key) {
180                 $this->bucket->delete($key);
181             }
182
183             return true;
184         } catch (Exception\RiakException $e) {
185             // Do nothing
186         }
187
188         return false;
189     }
190
191     /**
192      * {@inheritdoc}
193      */
194     protected function doGetStats()
195     {
196         // Only exposed through HTTP stats API, not Protocol Buffers API
197         return null;
198     }
199
200     /**
201      * Check if a given Riak Object have expired.
202      *
203      * @param \Riak\Object $object
204      *
205      * @return bool
206      */
207     private function isExpired(Object $object)
208     {
209         $metadataMap = $object->getMetadataMap();
210
211         return isset($metadataMap[self::EXPIRES_HEADER])
212             && $metadataMap[self::EXPIRES_HEADER] < time();
213     }
214
215     /**
216      * On-read conflict resolution. Applied approach here is last write wins.
217      * Specific needs may override this method to apply alternate conflict resolutions.
218      *
219      * {@internal Riak does not attempt to resolve a write conflict, and store
220      * it as sibling of conflicted one. By following this approach, it is up to
221      * the next read to resolve the conflict. When this happens, your fetched
222      * object will have a list of siblings (read as a list of objects).
223      * In our specific case, we do not care about the intermediate ones since
224      * they are all the same read from storage, and we do apply a last sibling
225      * (last write) wins logic.
226      * If by any means our resolution generates another conflict, it'll up to
227      * next read to properly solve it.}
228      *
229      * @param string $id
230      * @param string $vClock
231      * @param array  $objectList
232      *
233      * @return \Riak\Object
234      */
235     protected function resolveConflict($id, $vClock, array $objectList)
236     {
237         // Our approach here is last-write wins
238         $winner = $objectList[count($objectList)];
239
240         $putInput = new Input\PutInput();
241         $putInput->setVClock($vClock);
242
243         $mergedObject = new Object($id);
244         $mergedObject->setContent($winner->getContent());
245
246         $this->bucket->put($mergedObject, $putInput);
247
248         return $mergedObject;
249     }
250 }