Security update to Drupal 8.4.6
[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\Input;
24 use Riak\Exception;
25 use Riak\Object;
26
27 /**
28  * Riak cache provider.
29  *
30  * @link   www.doctrine-project.org
31  * @since  1.1
32  * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
33  */
34 class RiakCache extends CacheProvider
35 {
36     const EXPIRES_HEADER = 'X-Riak-Meta-Expires';
37
38     /**
39      * @var \Riak\Bucket
40      */
41     private $bucket;
42
43     /**
44      * Sets the riak bucket instance to use.
45      *
46      * @param \Riak\Bucket $bucket
47      */
48     public function __construct(Bucket $bucket)
49     {
50         $this->bucket = $bucket;
51     }
52
53     /**
54      * {@inheritdoc}
55      */
56     protected function doFetch($id)
57     {
58         try {
59             $response = $this->bucket->get($id);
60
61             // No objects found
62             if ( ! $response->hasObject()) {
63                 return false;
64             }
65
66             // Check for attempted siblings
67             $object = ($response->hasSiblings())
68                 ? $this->resolveConflict($id, $response->getVClock(), $response->getObjectList())
69                 : $response->getFirstObject();
70
71             // Check for expired object
72             if ($this->isExpired($object)) {
73                 $this->bucket->delete($object);
74
75                 return false;
76             }
77
78             return unserialize($object->getContent());
79         } catch (Exception\RiakException $e) {
80             // Covers:
81             // - Riak\ConnectionException
82             // - Riak\CommunicationException
83             // - Riak\UnexpectedResponseException
84             // - Riak\NotFoundException
85         }
86
87         return false;
88     }
89
90     /**
91      * {@inheritdoc}
92      */
93     protected function doContains($id)
94     {
95         try {
96             // We only need the HEAD, not the entire object
97             $input = new Input\GetInput();
98
99             $input->setReturnHead(true);
100
101             $response = $this->bucket->get($id, $input);
102
103             // No objects found
104             if ( ! $response->hasObject()) {
105                 return false;
106             }
107
108             $object = $response->getFirstObject();
109
110             // Check for expired object
111             if ($this->isExpired($object)) {
112                 $this->bucket->delete($object);
113
114                 return false;
115             }
116
117             return true;
118         } catch (Exception\RiakException $e) {
119             // Do nothing
120         }
121
122         return false;
123     }
124
125     /**
126      * {@inheritdoc}
127      */
128     protected function doSave($id, $data, $lifeTime = 0)
129     {
130         try {
131             $object = new Object($id);
132
133             $object->setContent(serialize($data));
134
135             if ($lifeTime > 0) {
136                 $object->addMetadata(self::EXPIRES_HEADER, (string) (time() + $lifeTime));
137             }
138
139             $this->bucket->put($object);
140
141             return true;
142         } catch (Exception\RiakException $e) {
143             // Do nothing
144         }
145
146         return false;
147     }
148
149     /**
150      * {@inheritdoc}
151      */
152     protected function doDelete($id)
153     {
154         try {
155             $this->bucket->delete($id);
156
157             return true;
158         } catch (Exception\BadArgumentsException $e) {
159             // Key did not exist on cluster already
160         } catch (Exception\RiakException $e) {
161             // Covers:
162             // - Riak\Exception\ConnectionException
163             // - Riak\Exception\CommunicationException
164             // - Riak\Exception\UnexpectedResponseException
165         }
166
167         return false;
168     }
169
170     /**
171      * {@inheritdoc}
172      */
173     protected function doFlush()
174     {
175         try {
176             $keyList = $this->bucket->getKeyList();
177
178             foreach ($keyList as $key) {
179                 $this->bucket->delete($key);
180             }
181
182             return true;
183         } catch (Exception\RiakException $e) {
184             // Do nothing
185         }
186
187         return false;
188     }
189
190     /**
191      * {@inheritdoc}
192      */
193     protected function doGetStats()
194     {
195         // Only exposed through HTTP stats API, not Protocol Buffers API
196         return null;
197     }
198
199     /**
200      * Check if a given Riak Object have expired.
201      *
202      * @param \Riak\Object $object
203      *
204      * @return bool
205      */
206     private function isExpired(Object $object) : bool
207     {
208         $metadataMap = $object->getMetadataMap();
209
210         return isset($metadataMap[self::EXPIRES_HEADER])
211             && $metadataMap[self::EXPIRES_HEADER] < time();
212     }
213
214     /**
215      * On-read conflict resolution. Applied approach here is last write wins.
216      * Specific needs may override this method to apply alternate conflict resolutions.
217      *
218      * {@internal Riak does not attempt to resolve a write conflict, and store
219      * it as sibling of conflicted one. By following this approach, it is up to
220      * the next read to resolve the conflict. When this happens, your fetched
221      * object will have a list of siblings (read as a list of objects).
222      * In our specific case, we do not care about the intermediate ones since
223      * they are all the same read from storage, and we do apply a last sibling
224      * (last write) wins logic.
225      * If by any means our resolution generates another conflict, it'll up to
226      * next read to properly solve it.}
227      *
228      * @param string $id
229      * @param string $vClock
230      * @param array  $objectList
231      *
232      * @return \Riak\Object
233      */
234     protected function resolveConflict($id, $vClock, array $objectList)
235     {
236         // Our approach here is last-write wins
237         $winner = $objectList[count($objectList)];
238
239         $putInput = new Input\PutInput();
240         $putInput->setVClock($vClock);
241
242         $mergedObject = new Object($id);
243         $mergedObject->setContent($winner->getContent());
244
245         $this->bucket->put($mergedObject, $putInput);
246
247         return $mergedObject;
248     }
249 }