1: <?php
2: /**
3: * Copyright 2012-2014 Rackspace US, Inc.
4: *
5: * Licensed under the Apache License, Version 2.0 (the "License");
6: * you may not use this file except in compliance with the License.
7: * You may obtain a copy of the License at
8: *
9: * http://www.apache.org/licenses/LICENSE-2.0
10: *
11: * Unless required by applicable law or agreed to in writing, software
12: * distributed under the License is distributed on an "AS IS" BASIS,
13: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14: * See the License for the specific language governing permissions and
15: * limitations under the License.
16: */
17:
18: namespace OpenCloud\ObjectStore\Resource;
19:
20: use Guzzle\Http\Message\Response;
21: use OpenCloud\Common\Base;
22: use OpenCloud\Common\Service\ServiceInterface;
23:
24: /**
25: * Abstract base class which implements shared functionality of ObjectStore
26: * resources. Provides support, for example, for metadata-handling and other
27: * features that are common to the ObjectStore components.
28: */
29: abstract class AbstractResource extends Base
30: {
31: const GLOBAL_METADATA_PREFIX = 'X';
32:
33: /** @var \OpenCloud\Common\Metadata */
34: protected $metadata;
35:
36: /** @var string The FQCN of the metadata object used for the container. */
37: protected $metadataClass = 'OpenCloud\\Common\\Metadata';
38:
39: /** @var \OpenCloud\Common\Service\ServiceInterface The service object. */
40: protected $service;
41:
42: public function __construct(ServiceInterface $service)
43: {
44: $this->service = $service;
45: $this->metadata = new $this->metadataClass;
46: }
47:
48: /**
49: * For internal use only.
50: *
51: * @return Service The ObjectStore service associated with this ObjectStore resource.
52: */
53: public function getService()
54: {
55: return $this->service;
56: }
57:
58: /**
59: * For internal use only.
60: *
61: * @return Service The CDN version of the ObjectStore service associated with this ObjectStore resource.
62: */
63: public function getCdnService()
64: {
65: return $this->service->getCDNService();
66: }
67:
68: /**
69: * For internal use only.
70: *
71: * @return Client The HTTP client associated with the associated ObjectStore service.
72: */
73: public function getClient()
74: {
75: return $this->service->getClient();
76: }
77:
78: /**
79: * Factory method that allows for easy instantiation from a Response object.
80: *
81: * For internal use only.
82: *
83: * @param Response $response HTTP response from an API operation.
84: * @param ServiceInterface $service The ObjectStore service to associate with this ObjectStore resource object.
85: * @return AbstractResource A concrete sub-class of `AbstractResource`.
86: */
87: public static function fromResponse(Response $response, ServiceInterface $service)
88: {
89: $object = new static($service);
90:
91: if (null !== ($headers = $response->getHeaders())) {
92: $object->setMetadata($headers, true);
93: }
94:
95: return $object;
96: }
97:
98: /**
99: * Trim headers of their resource-specific prefixes.
100: *
101: * For internal use only.
102: *
103: * @param array $headers Headers as returned from an HTTP response
104: * @return array Trimmed headers
105: */
106: public static function trimHeaders($headers)
107: {
108: $output = array();
109:
110: foreach ($headers as $header => $value) {
111: // Only allow allow X-<keyword>-* headers to pass through after stripping them
112: if (static::headerIsValidMetadata($header) && ($key = self::stripPrefix($header))) {
113: $output[$key] = (string) $value;
114: }
115: }
116:
117: return $output;
118: }
119:
120: protected static function headerIsValidMetadata($header)
121: {
122: $pattern = sprintf('#^%s\-#i', self::GLOBAL_METADATA_PREFIX);
123:
124: return preg_match($pattern, $header);
125: }
126:
127: /**
128: * Strip an individual header name of its resource-specific prefix.
129: *
130: * @param $header
131: * @return mixed
132: */
133: protected static function stripPrefix($header)
134: {
135: $pattern = '#^' . self::GLOBAL_METADATA_PREFIX . '\-(' . static::METADATA_LABEL . '-)?(Meta-)?#i';
136:
137: return preg_replace($pattern, '', $header);
138: }
139:
140: /**
141: * Prepend/stock the header names with a resource-specific prefix.
142: *
143: * @param array $headers Headers to use on ObjectStore resource.
144: * @return array Headers returned with appropriate prefix as expected by ObjectStore service.
145: */
146: public static function stockHeaders(array $headers)
147: {
148: $output = array();
149: $prefix = null;
150: $corsHeaders = array(
151: 'Access-Control-Allow-Origin',
152: 'Access-Control-Expose-Headers',
153: 'Access-Control-Max-Age',
154: 'Access-Control-Allow-Credentials',
155: 'Access-Control-Allow-Methods',
156: 'Access-Control-Allow-Headers'
157: );
158: foreach ($headers as $header => $value) {
159: if (!in_array($header, $corsHeaders)) {
160: $prefix = self::GLOBAL_METADATA_PREFIX . '-' . static::METADATA_LABEL . '-Meta-';
161: }
162: $output[$prefix . $header] = $value;
163: }
164:
165: return $output;
166: }
167:
168: /**
169: * Set the metadata (local-only) for this object. You must call saveMetadata
170: * to actually persist the metadata using the ObjectStore service.
171: *
172: * @param array $data Object/container metadata key/value pair array.
173: * @param bool $constructFromResponse Whether the metadata key/value pairs were obtiained from an HTTP response of an ObjectStore API operation.
174: * @return AbstractResource This object, with metadata set.
175: */
176: public function setMetadata($data, $constructFromResponse = false)
177: {
178: if ($constructFromResponse) {
179: $metadata = new $this->metadataClass;
180: $metadata->setArray(self::trimHeaders($data));
181: $data = $metadata;
182: }
183:
184: $this->metadata = $data;
185:
186: return $this;
187: }
188:
189: /**
190: * Returns metadata for this object.
191: *
192: * @return \OpenCloud\Common\Metadata Metadata set on this object.
193: */
194: public function getMetadata()
195: {
196: return $this->metadata;
197: }
198:
199: /**
200: * Push local metadata to the API, thereby executing a permanent save.
201: *
202: * @param array $metadata The array of values you want to set as metadata
203: * @param bool $stockPrefix Whether to prepend each array key with the metadata-specific prefix. For objects, this
204: * would be X-Object-Meta-Foo => Bar
205: * @return Response HTTP response from API operation.
206: */
207: public function saveMetadata(array $metadata, $stockPrefix = true)
208: {
209: $headers = ($stockPrefix === true) ? self::stockHeaders($metadata) : $metadata;
210:
211: return $this->getClient()->post($this->getUrl(), $headers)->send();
212: }
213:
214: /**
215: * Retrieve metadata from the API. This method will then set and return this value.
216: *
217: * @return \OpenCloud\Common\Metadata Metadata returned from the ObjectStore service for this object/container.
218: */
219: public function retrieveMetadata()
220: {
221: $response = $this->getClient()
222: ->head($this->getUrl())
223: ->send();
224:
225: $this->setMetadata($response->getHeaders(), true);
226:
227: return $this->metadata;
228: }
229:
230: /**
231: * To delete or unset a particular metadata item.
232: *
233: * @param $key Metadata key to unset
234: * @return Response HTTP response returned from API operation to unset metadata item.
235: */
236: public function unsetMetadataItem($key)
237: {
238: $header = sprintf('%s-Remove-%s-Meta-%s', self::GLOBAL_METADATA_PREFIX,
239: static::METADATA_LABEL, $key);
240:
241: $headers = array($header => 'True');
242:
243: return $this->getClient()
244: ->post($this->getUrl(), $headers)
245: ->send();
246: }
247:
248: /**
249: * Append a particular array of values to the existing metadata. Analogous
250: * to a merge. You must call to actually persist the metadata using the
251: * ObjectStore service.
252: *
253: * @param array $values The array of values you want to append to metadata.
254: * @return array Metadata, after `$values` are appended.
255: */
256: public function appendToMetadata(array $values)
257: {
258: return (!empty($this->metadata) && is_array($this->metadata))
259: ? array_merge($this->metadata, $values)
260: : $values;
261: }
262: }
263: