PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/OData Producer for PHP/library/ODataProducer/Providers/Stream/DataServiceStreamProviderWrapper.php

#
PHP | 542 lines | 369 code | 20 blank | 153 comment | 24 complexity | 486fe21a14ec2ba7b9b35f1769dc3c73 MD5 | raw file
  1. <?php
  2. /**
  3. * Wrapper over IDSSP and IDSSP2 implementations.
  4. *
  5. * PHP version 5.3
  6. *
  7. * @category ODataProducer
  8. * @package ODataProducer_Providers_Stream
  9. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  10. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  11. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  12. * @version SVN: 1.0
  13. * @link http://odataphpproducer.codeplex.com
  14. *
  15. */
  16. namespace ODataProducer\Providers\Stream;
  17. use ODataProducer\DataService;
  18. use ODataProducer\Providers\Metadata\ResourceStreamInfo;
  19. use ODataProducer\OperationContext\DataServiceHost;
  20. use ODataProducer\Common\Version;
  21. use ODataProducer\Common\ODataException;
  22. use ODataProducer\Common\ODataConstants;
  23. use ODataProducer\Common\Messages;
  24. use ODataProducer\Common\InvalidOperationException;
  25. /**
  26. * Wrapper over IDSSP and IDSSP2 implementations.
  27. *
  28. * @category ODataProducer
  29. * @package ODataProducer_Providers_Stream
  30. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  31. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  32. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  33. * @version Release: 1.0
  34. * @link http://odataphpproducer.codeplex.com
  35. */
  36. class DataServiceStreamProviderWrapper
  37. {
  38. /**
  39. * Holds reference to the data service instance.
  40. *
  41. * @var DataService
  42. */
  43. private $_dataService;
  44. /**
  45. * Holds reference to the implementation of IDataServiceStreamProvider
  46. * or IDataServiceStreamProvider2.
  47. *
  48. * @var IDataServiceStreamProvider/IDataServiceStreamProvider2
  49. */
  50. private $_streamProvider;
  51. /**
  52. * Used to check whether interface implementor modified repsonse content
  53. * type header or not.
  54. *
  55. * @var string/NULL
  56. */
  57. private $_responseContentType;
  58. /**
  59. * Used to check whether interface implementor modified etag header or not.
  60. *
  61. * @var string/NULL
  62. */
  63. private $_responseETag;
  64. /**
  65. * Constructs a new instance of DataServiceStreamProviderWrapper
  66. */
  67. public function __construct()
  68. {
  69. }
  70. /**
  71. * To set reference to the data service instance.
  72. *
  73. * @param DataService &$dataService The data service instance.
  74. *
  75. * @return void
  76. */
  77. public function setDataService(DataService &$dataService)
  78. {
  79. $this->_dataService = $dataService;
  80. }
  81. /**
  82. * To get stream associated with the given media resource.
  83. *
  84. * @param object $entity The media resource.
  85. * @param ResourceStreamInfo $resourceStreamInfo This will be null if media
  86. * resource is MLE, if media
  87. * resource is named
  88. * stream then will be the
  89. * ResourceStreamInfo instance
  90. * holding the details of
  91. * named stream.
  92. *
  93. * @return string/NULL
  94. */
  95. public function getReadStream($entity, $resourceStreamInfo)
  96. {
  97. $requestETag = null;
  98. $checkETagForEquality = null;
  99. $this->_getETagFromHeaders($requestETag, $checkETagForEquality);
  100. $stream = null;
  101. try {
  102. $this->_saveContentTypeAndETag();
  103. if (is_null($resourceStreamInfo)) {
  104. $this->_loadAndValidateStreamProvider();
  105. $stream = $this->_streamProvider->getReadStream(
  106. $entity,
  107. $requestETag,
  108. $checkETagForEquality,
  109. $this->_dataService->getOperationContext()
  110. );
  111. } else {
  112. $this->_loadAndValidateStreamProvider2();
  113. $stream = $this->_streamProvider->getReadStream2(
  114. $entity,
  115. $resourceStreamInfo,
  116. $requestETag,
  117. $checkETagForEquality,
  118. $this->_dataService->getOperationContext()
  119. );
  120. }
  121. $this->_verifyContentTypeOrETagModified('IDSSP::getReadStream');
  122. } catch(ODataException $odataException) {
  123. //Check for status code 304 (stream Not Modified)
  124. if ($odataException->getStatusCode() == 304) {
  125. $eTag = $this->getStreamETag($entity, $resourceStreamInfo);
  126. if (!is_null($eTag)) {
  127. $this->_dataService->getHost()->setResponseETag($eTag);
  128. }
  129. }
  130. throw $odataException;
  131. }
  132. if ($resourceStreamInfo == null) {
  133. // For default streams, we always expect getReadStream()
  134. // to return a non-null stream.
  135. if (is_null($stream)) {
  136. throw new InvalidOperationException(
  137. Messages::dataServiceStreamProviderWrapperInvalidStreamFromGetReadStream()
  138. );
  139. }
  140. } else {
  141. // For named streams, getReadStream() can return null to indicate
  142. // that the stream has not been created.
  143. if (is_null($stream)) {
  144. // 204 == no content
  145. $this->_dataService->getHost()->setResponseStatusCode(204);
  146. }
  147. }
  148. return $stream;
  149. }
  150. /**
  151. * Gets the IANA content type (aka media type) of the stream associated with
  152. * the specified media resource.
  153. *
  154. * @param object $entity The entity instance
  155. * (media resource) associated with
  156. * the stream for which the content
  157. * type is to be obtained.
  158. * @param ResourceStreamInfo $resourceStreamInfo This will be null if
  159. * media resource is MLE,
  160. * if media resource is named
  161. * stream then will be the
  162. * ResourceStreamInfo instance
  163. * holding the details of
  164. * named stream.
  165. *
  166. * @return string/NULL
  167. */
  168. public function getStreamContentType($entity, $resourceStreamInfo)
  169. {
  170. $contentType = null;
  171. $this->_saveContentTypeAndETag();
  172. if (is_null($resourceStreamInfo)) {
  173. $this->_loadAndValidateStreamProvider();
  174. $contentType = $this->_streamProvider->getStreamContentType(
  175. $entity,
  176. $this->_dataService->getOperationContext()
  177. );
  178. if (is_null($contentType)) {
  179. throw new InvalidOperationException(
  180. Messages::dataServiceStreamProviderWrapperGetStreamContentTypeReturnsEmptyOrNull()
  181. );
  182. }
  183. } else {
  184. $this->_loadAndValidateStreamProvider2();
  185. $contentType = $this->_streamProvider->getStreamContentType2(
  186. $entity,
  187. $resourceStreamInfo,
  188. $this->_dataService->getOperationContext()
  189. );
  190. }
  191. $this->_verifyContentTypeOrETagModified('IDSSP::getStreamContentType');
  192. return $contentType;
  193. }
  194. /**
  195. * Get the ETag of the stream associated with the entity specified.
  196. *
  197. * @param object $entity The entity instance
  198. * (media resource) associated
  199. * with the stream for which
  200. * the etag is to be obtained.
  201. * @param ResourceStreamInfo $resourceStreamInfo This will be null if
  202. * media resource is MLE,
  203. * if media resource is named
  204. * stream then will be the
  205. * ResourceStreamInfo
  206. * instance holding the
  207. * details of named stream.
  208. *
  209. * @throws InvalidOperationException
  210. * @return String Etag
  211. */
  212. public function getStreamETag($entity, $resourceStreamInfo)
  213. {
  214. $eTag = null;
  215. $this->_saveContentTypeAndETag();
  216. if (is_null($resourceStreamInfo)) {
  217. $this->_loadAndValidateStreamProvider();
  218. $eTag = $this->_streamProvider->getStreamETag(
  219. $entity,
  220. $this->_dataService->getOperationContext()
  221. );
  222. } else {
  223. $this->_loadAndValidateStreamProvider2();
  224. $eTag = $this->_streamProvider->getStreamETag2(
  225. $entity,
  226. $resourceStreamInfo,
  227. $this->_dataService->getOperationContext()
  228. );
  229. }
  230. $this->_verifyContentTypeOrETagModified('IDSSP::getStreamETag');
  231. if (!self::isETagValueValid($eTag, true)) {
  232. throw new InvalidOperationException(
  233. Messages::dataServiceStreamProviderWrapperGetStreamETagReturnedInvalidETagFormat()
  234. );
  235. }
  236. return $eTag;
  237. }
  238. /**
  239. * Gets the URI clients should use when making retrieve (ie. GET) requests
  240. * to the stream.
  241. *
  242. * @param object $entity The entity instance
  243. * associated with the
  244. * stream for which a
  245. * read stream URI is to
  246. * be obtained.
  247. * @param ResourceStreamInfo $resourceStreamInfo This will be null
  248. * if media resource
  249. * is MLE, if media
  250. * resource is named
  251. * stream then will be
  252. * the ResourceStreamInfo
  253. * instance holding the
  254. * details of named stream.
  255. * @param string $mediaLinkEntryUri MLE uri.
  256. *
  257. * @return string
  258. *
  259. * @throws InvalidOperationException
  260. */
  261. public function getReadStreamUri($entity, $resourceStreamInfo,
  262. $mediaLinkEntryUri
  263. ) {
  264. $readStreamUri = null;
  265. $this->_saveContentTypeAndETag();
  266. if (is_null($resourceStreamInfo)) {
  267. $this->_loadAndValidateStreamProvider();
  268. $readStreamUri = $this->_streamProvider->getReadStreamUri(
  269. $entity,
  270. $this->_dataService->getOperationContext()
  271. );
  272. } else {
  273. $this->_loadAndValidateStreamProvider2();
  274. $readStreamUri = $this->_streamProvider->getReadStreamUri2(
  275. $entity,
  276. $resourceStreamInfo,
  277. $this->_dataService->getOperationContext()
  278. );
  279. }
  280. $this->_verifyContentTypeOrETagModified('IDSSP::getReadStreamUri');
  281. if (!is_null($readStreamUri)) {
  282. try {
  283. new ODataProducer\Common\Url($readStreamUri);
  284. } catch (\ODataProducer\Common\UrlFormatException $ex) {
  285. throw new InvalidOperationException(
  286. Messages::dataServiceStreamProviderWrapperGetReadStreamUriMustReturnAbsoluteUriOrNull()
  287. );
  288. }
  289. } else {
  290. if (is_null($resourceStreamInfo)) {
  291. // For MLEs the content src attribute is
  292. //required so we cannot return null.
  293. $readStreamUri
  294. = $this->getDefaultStreamEditMediaUri(
  295. $mediaLinkEntryUri,
  296. null
  297. );
  298. }
  299. }
  300. // Note if readStreamUri is null, the self link for the
  301. // named stream will be omitted.
  302. return $readStreamUri;
  303. }
  304. /**
  305. * Checks the given value is a valid eTag.
  306. *
  307. * @param string $etag eTag to validate.
  308. * @param boolean $allowStrongEtag True if strong eTag is allowed
  309. * False otherwise.
  310. *
  311. * @return boolean
  312. */
  313. public static function isETagValueValid($etag, $allowStrongEtag)
  314. {
  315. if (is_null($etag) || $etag === '*') {
  316. return true;
  317. }
  318. // HTTP RFC 2616, section 3.11:
  319. // entity-tag = [ weak ] opaque-tag
  320. // weak = "W/"
  321. // opaque-tag = quoted-string
  322. $etagValueStartIndex = 1;
  323. $eTagLength = strlen($etag);
  324. if (strpos($etag, "W/\"") === 0 && $etag[$eTagLength - 1] == '"') {
  325. $etagValueStartIndex = 3;
  326. } else if (!$allowStrongEtag || $etag[0] != '"'
  327. || $etag[$eTagLength - 1] != '"'
  328. ) {
  329. return false;
  330. }
  331. for ($i = $etagValueStartIndex; $i < $eTagLength - 1; $i++) {
  332. // Format of etag looks something like: W/"etag property values"
  333. // or "strong etag value" according to HTTP RFC 2616, if someone
  334. // wants to specify more than 1 etag value, then need to specify
  335. // something like this: W/"etag values", W/"etag values", ...
  336. // To make sure only one etag is specified, we need to ensure
  337. // that if " is part of the key value, it needs to be escaped.
  338. if ($etag[$i] == '"') {
  339. return false;
  340. }
  341. }
  342. return true;
  343. }
  344. /**
  345. * Get ETag header value from request header.
  346. *
  347. * @param mixed &$eTag On return, this parameter will hold
  348. * value of IfMatch or IfNoneMatch
  349. * header, if this header is absent then
  350. * this parameter will hold NULL.
  351. * @param mixed &$checkETagForEquality On return, this parameter will hold
  352. * true if IfMatch is present, false if
  353. * IfNoneMatch header is present, null
  354. * otherwise.
  355. *
  356. * @return void
  357. */
  358. private function _getETagFromHeaders(&$eTag, &$checkETagForEquality)
  359. {
  360. $dataServiceHost = $this->_dataService->getHost();
  361. //TODO Do check for mutual exclusion of RequestIfMatch and
  362. //RequestIfNoneMatch in DataServiceHost
  363. $eTag = $dataServiceHost->getRequestIfMatch();
  364. if (!is_null($eTag)) {
  365. $checkETagForEquality = true;
  366. return;
  367. }
  368. $eTag = $dataServiceHost->getRequestIfNoneMatch();
  369. if (!is_null($eTag)) {
  370. $checkETagForEquality = false;
  371. return;
  372. }
  373. $checkETagForEquality = null;
  374. }
  375. /**
  376. * Validates that an implementation of IDataServiceStreamProvider exists and
  377. * load it.
  378. *
  379. * @return void
  380. *
  381. * @throws ODataException
  382. */
  383. private function _loadAndValidateStreamProvider()
  384. {
  385. if (is_null($this->_streamProvider)) {
  386. $this->_loadStreamProvider();
  387. if (is_null($this->_streamProvider)) {
  388. ODataException::createInternalServerError(
  389. Messages::dataServiceStreamProviderWrapperMustImplementIDataServiceStreamProviderToSupportStreaming()
  390. );
  391. }
  392. }
  393. }
  394. /**
  395. * Validates that an implementation of IDataServiceStreamProvider2 exists and
  396. * load it.
  397. *
  398. * @return void
  399. *
  400. * @throws ODataException
  401. */
  402. private function _loadAndValidateStreamProvider2()
  403. {
  404. $maxServiceVersion = $this->_dataService
  405. ->getServiceConfiguration()
  406. ->getMaxDataServiceVersionObject();
  407. if ($maxServiceVersion->compare(new Version(3, 0)) < 0) {
  408. ODataException::createInternalServerError(
  409. Messages::dataServiceStreamProviderWrapperMaxProtocolVersionMustBeV3OrAboveToSupportNamedStreams()
  410. );
  411. }
  412. if (is_null($this->_streamProvider)) {
  413. $this->_loadStreamProvider();
  414. if (is_null($this->_streamProvider)) {
  415. ODataException::createInternalServerError(
  416. Messages::dataServiceStreamProviderWrapperMustImplementDataServiceStreamProvider2ToSupportNamedStreams()
  417. );
  418. } else if (array_search('ODataProducer\Providers\Stream\IDataServiceStreamProvider2', class_implements($this->_streamProvider)) === false) {
  419. ODataException::createInternalServerError(
  420. Messages::dataServiceStreamProviderWrapperInvalidStream2Instance()
  421. );
  422. }
  423. }
  424. }
  425. /**
  426. * Ask data service to load stream provider instance.
  427. *
  428. * @return void
  429. *
  430. * @throws ODataException
  431. */
  432. private function _loadStreamProvider()
  433. {
  434. if (is_null($this->_streamProvider)) {
  435. $maxServiceVersion = $this->_dataService
  436. ->getServiceConfiguration()
  437. ->getMaxDataServiceVersionObject();
  438. if ($maxServiceVersion->compare(new Version(3, 0)) >= 0) {
  439. $this->_streamProvider
  440. = $this->_dataService->getService('IDataServiceStreamProvider2');
  441. if (!is_null($this->_streamProvider) && (!is_object($this->_streamProvider) || array_search('ODataProducer\Providers\Stream\IDataServiceStreamProvider2', class_implements($this->_streamProvider)) === false)) {
  442. ODataException::createInternalServerError(
  443. Messages::dataServiceStreamProviderWrapperInvalidStream2Instance()
  444. );
  445. }
  446. }
  447. if (is_null($this->_streamProvider)) {
  448. $this->_streamProvider
  449. = $this->_dataService->getService('IDataServiceStreamProvider');
  450. if (!is_null($this->_streamProvider) && (!is_object($this->_streamProvider) || array_search('ODataProducer\Providers\Stream\IDataServiceStreamProvider', class_implements($this->_streamProvider)) === false)) {
  451. ODataException::createInternalServerError(
  452. Messages::dataServiceStreamProviderWrapperInvalidStreamInstance()
  453. );
  454. }
  455. }
  456. }
  457. }
  458. /**
  459. * Construct the default edit media uri from the given media link entry uri.
  460. *
  461. * @param string $mediaLinkEntryUri Uri to the media link entry.
  462. * @param ResourceStremaInfo $resourceStreamInfo Stream info instance, if its
  463. * null default stream is assumed.
  464. *
  465. * @return string Uri to the media resource.
  466. */
  467. public function getDefaultStreamEditMediaUri($mediaLinkEntryUri, $resourceStreamInfo)
  468. {
  469. if (is_null($resourceStreamInfo)) {
  470. return rtrim($mediaLinkEntryUri, '/') . '/' . ODataConstants::URI_VALUE_SEGMENT;
  471. } else {
  472. return rtrim($mediaLinkEntryUri, '/') . '/' . ltrim($resourceStreamInfo->getName(), '/');
  473. }
  474. }
  475. /**
  476. * Save value of content type and etag headers before invoking implementor
  477. * methods.
  478. *
  479. * @return void
  480. */
  481. private function _saveContentTypeAndETag()
  482. {
  483. $this->_responseContentType
  484. = $this->_dataService->getHost()->getResponseContentType();
  485. $this->_responseETag
  486. = $this->_dataService->getHost()->getResponseETag();
  487. }
  488. /**
  489. * Check whether implementor modified content type or etag header
  490. * if so throw InvalidOperationException.
  491. *
  492. * @param string $methodName NAme of the method
  493. *
  494. * @return void
  495. *
  496. * @throws InvalidOperationException
  497. */
  498. private function _verifyContentTypeOrETagModified($methodName)
  499. {
  500. if ($this->_responseContentType !== $this->_dataService->getHost()->getResponseContentType()
  501. || $this->_responseETag !== $this->_dataService->getHost()->getResponseETag()
  502. ) {
  503. throw new InvalidOperationException(
  504. Messages::dataServiceStreamProviderWrapperMustNotSetContentTypeAndEtag($methodName)
  505. );
  506. }
  507. }
  508. }
  509. ?>