PageRenderTime 56ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/OData Producer for PHP/library/ODataProducer/DataService.php

#
PHP | 963 lines | 541 code | 60 blank | 362 comment | 122 complexity | e3638bfd9006c3284d392a289492a55c MD5 | raw file
  1. <?php
  2. /**
  3. * The base class for all DataService specific classes. This class implements
  4. * the following interfaces:
  5. * (1) IRequestHandler
  6. * Implementing this interface requires defining the function
  7. * 'handleRequest' that will be invoked by dispatcher
  8. * (2) IDataService
  9. * Force DataService class to implement functions for custom
  10. * data service providers
  11. *
  12. * PHP version 5.3
  13. *
  14. * @category ODataProducer
  15. * @package ODataProducer
  16. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  17. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  18. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  19. * @version SVN: 1.0
  20. * @link http://odataphpproducer.codeplex.com
  21. *
  22. */
  23. namespace ODataProducer;
  24. use ODataProducer\Providers\Metadata\ResourceTypeKind;
  25. use ODataProducer\ObjectModel\ODataPropertyContent;
  26. use ODataProducer\Common\ErrorHandler;
  27. use ODataProducer\Common\Messages;
  28. use ODataProducer\Common\ODataException;
  29. use ODataProducer\Common\ODataConstants;
  30. use ODataProducer\Common\NotImplementedException;
  31. use ODataProducer\Common\InvalidOperationException;
  32. use ODataProducer\Common\HttpStatus;
  33. use ODataProducer\Providers\MetadataQueryProviderWrapper;
  34. use ODataProducer\Providers\Stream\DataServiceStreamProviderWrapper;
  35. use ODataProducer\Configuration\DataServiceConfiguration;
  36. use ODataProducer\UriProcessor\UriProcessor;
  37. use ODataProducer\UriProcessor\RequestDescription;
  38. use ODataProducer\UriProcessor\ResourcePathProcessor\SegmentParser\RequestTargetKind;
  39. use ODataProducer\OperationContext\DataServiceHost;
  40. use ODataProducer\Providers\Metadata\ResourceType;
  41. use ODataProducer\Providers\Metadata\Type\Binary;
  42. use ODataProducer\ObjectModel\ObjectModelSerializer;
  43. use ODataProducer\Writers\ResponseWriter;
  44. /**
  45. * The DataService base class.
  46. *
  47. * @category ODataProducer
  48. * @package ODataProducer
  49. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  50. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  51. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  52. * @version Release: 1.0
  53. * @link http://odataphpproducer.codeplex.com
  54. */
  55. abstract class DataService implements IRequestHandler, IDataService
  56. {
  57. /**
  58. * To hold reference to DataServiceProviderWrapper which is a wrapper
  59. * over IDataServiceQueryProvider and IDataServiceMetadataProvider
  60. * Implementation.
  61. *
  62. * @var MetadataQueryProviderWrapper
  63. */
  64. private $_metadataQueryProviderWrapper;
  65. /**
  66. * To hold reference to DataServiceStreamProviderWrapper, which is a
  67. * wrapper over IDataServiceServiceProvider implementation
  68. *
  69. * @var DataServiceStreamProviderWrapper
  70. */
  71. private $_dataServiceStreamProvider;
  72. /**
  73. * Hold reference to the DataServiceHost instance created by dispatcher,
  74. * using this library can access headers and body of Http Request
  75. * dispatcher received and the Http Response Dispatcher is going to send.
  76. *
  77. * @var DataServiceHost
  78. */
  79. private $_dataServiceHost;
  80. /**
  81. * To hold reference to DataServiceOperationContext, using this we can
  82. * access headers and body of Http Request dispatcher received and the
  83. * Http Response Dispatcher is going to send.
  84. *
  85. * @var DataServiceOperationContext
  86. */
  87. private $_dataServiceOperationContext;
  88. /**
  89. * To hold reference to DataServiceConfiguration instance where the
  90. * service specific rules (page limit, resource set access rights
  91. * etc...) are defined.
  92. *
  93. * @var DataServiceConfiguration
  94. */
  95. private $_dataServiceConfiguration;
  96. /**
  97. * Gets reference to DataServiceConfiguration instance so that
  98. * service specific rules defined by the developer can be
  99. * accessed.
  100. *
  101. * @return DataServiceConfiguration
  102. */
  103. public function getServiceConfiguration()
  104. {
  105. return $this->_dataServiceConfiguration;
  106. }
  107. /**
  108. * To get the reference to DataServiceProviderWrapper, a wrapper
  109. * over developer's IDataServiceQueryProvider and
  110. * IDataServiceMetadataProvider implementation.
  111. *
  112. * @return MetadataQueryProviderWrapper
  113. */
  114. public function getMetadataQueryProviderWrapper()
  115. {
  116. return $this->_metadataQueryProviderWrapper;
  117. }
  118. /**
  119. * Gets reference to wrapper class instance over IDSSP implementation
  120. *
  121. * @return DataServiceStreamProviderWrapper
  122. */
  123. public function getStreamProviderWrapper()
  124. {
  125. return $this->_dataServiceStreamProvider;
  126. }
  127. /**
  128. * Get reference to the data service host instance.
  129. *
  130. * @return DataServiceHost
  131. */
  132. public function getHost()
  133. {
  134. return $this->_dataServiceHost;
  135. }
  136. /**
  137. * Sets the data service host instance.
  138. *
  139. * @param DataServiceHost $dataServiceHost The data service host instance.
  140. *
  141. * @return void
  142. */
  143. public function setHost(DataServiceHost $dataServiceHost)
  144. {
  145. $this->_dataServiceHost = $dataServiceHost;
  146. }
  147. /**
  148. * To get reference to operation context where we have direct access to
  149. * headers and body of Http Request we have received and the Http Response
  150. * We are going to send.
  151. *
  152. * @return WebOperationContext
  153. */
  154. public function getOperationContext()
  155. {
  156. return $this->_dataServiceHost->getWebOperationContext();
  157. }
  158. /**
  159. * Get reference to the wrapper over IDataServiceStreamProvider or
  160. * IDataServiceStreamProvider2 implementations.
  161. *
  162. * @return DataServiceStreamProviderWrapper
  163. */
  164. public function getStreamProvider()
  165. {
  166. if (is_null($this->_dataServiceStreamProvider)) {
  167. $this->_dataServiceStreamProvider = new DataServiceStreamProviderWrapper();
  168. $this->_dataServiceStreamProvider->setDataService($this);
  169. }
  170. return $this->_dataServiceStreamProvider;
  171. }
  172. /**
  173. * Top-level handler invoked by Dispatcher against any request to this
  174. * service. This method will hand over request processing task to other
  175. * functions which process the request, set required headers and Response
  176. * stream (if any in Atom/Json format) in
  177. * WebOperationContext::Current()::OutgoingWebResponseContext.
  178. * Once this function returns, dispatcher uses global WebOperationContext
  179. * to write out the request response to client.
  180. * This function will perform the following operations:
  181. * (1) Check whether the top level service class implements
  182. * IServiceProvider which means the service is a custom service, in
  183. * this case make sure the top level service class implements
  184. * IDataServiceMetaDataProvider and IDataServiceQueryProvider.
  185. * These are the minimal interfaces that a custom service to be
  186. * implemented in order to expose its data as OData. Save reference to
  187. * These interface implementations.
  188. * NOTE: Here we will ensure only providers for IDSQP and IDSMP. The
  189. * IDSSP will be ensured only when there is an GET request on MLE/Named
  190. * stream.
  191. *
  192. * (2). Invoke 'InitializeService' method of top level service for
  193. * collecting the configuration rules set by the developer for this
  194. * service.
  195. *
  196. * (3). Invoke the Uri processor to process the request URI. The uri
  197. * processor will do the following:
  198. * (a). Validate the request uri syntax using OData uri rules
  199. * (b). Validate the request using metadata of this service
  200. * (c). Parse the request uri and using, IDataServiceQueryProvider
  201. * implementation, fetches the resources pointed by the uri
  202. * if required
  203. * (d). Build a RequestDescription which encapsulate everything
  204. * related to request uri (e.g. type of resource, result
  205. * etc...)
  206. * (3). Invoke handleRequest2 for further processing
  207. *
  208. * @return void
  209. */
  210. public function handleRequest()
  211. {
  212. try {
  213. $this->createProviders();
  214. $this->_dataServiceHost->validateQueryParameters();
  215. $requestMethod = $this->getOperationContext()->incomingRequest()->getMethod();
  216. if ($requestMethod !== ODataConstants::HTTP_METHOD_GET) {
  217. ODataException::createNotImplementedError(Messages::dataServiceOnlyReadSupport($requestMethod));
  218. }
  219. } catch (\Exception $exception) {
  220. ErrorHandler::handleException($exception, $this);
  221. // Return to dispatcher for writing serialized exception
  222. return;
  223. }
  224. $uriProcessor = null;
  225. try {
  226. $uriProcessor = UriProcessor::process($this);
  227. $requestDescription = $uriProcessor->getRequestDescription();
  228. $this->serializeResult($requestDescription, $uriProcessor);
  229. } catch (\Exception $exception) {
  230. ErrorHandler::handleException($exception, $this);
  231. // Return to dispatcher for writing serialized exception
  232. return;
  233. }
  234. // Return to dispatcher for writing result
  235. }
  236. /**
  237. * This method will query and validates for IDataServiceMetadataProvider and
  238. * IDataServiceQueryProvider implementations, invokes
  239. * DataService::InitializeService to initialize service specific policies.
  240. *
  241. * @return void
  242. *
  243. * @throws ODataException
  244. */
  245. protected function createProviders()
  246. {
  247. if (array_search('ODataProducer\IServiceProvider', class_implements($this)) === false) {
  248. ODataException::createInternalServerError(
  249. Messages::dataServiceNotImplementsIServiceProvider()
  250. );
  251. }
  252. $metadataProvider = $this->getService('IDataServiceMetadataProvider');
  253. if (is_null($metadataProvider)) {
  254. ODataException::createInternalServerError(
  255. Messages::dataServiceMetadataQueryProviderNull()
  256. );
  257. }
  258. if (!is_object($metadataProvider)
  259. || array_search('ODataProducer\Providers\Metadata\IDataServiceMetadataProvider', class_implements($metadataProvider)) === false
  260. ) {
  261. ODataException::createInternalServerError(
  262. Messages::dataServiceInvalidMetadataInstance()
  263. );
  264. }
  265. $expectingQP2 = false;
  266. $queryProvider = $this->getService('IDataServiceQueryProvider');
  267. if (is_null($queryProvider)) {
  268. $expectingQP2 = true;
  269. $queryProvider = $this->getService('IDataServiceQueryProvider2');
  270. }
  271. if (is_null($queryProvider)) {
  272. ODataException::createInternalServerError(
  273. Messages::dataServiceMetadataQueryProviderNull()
  274. );
  275. }
  276. if (!is_object($queryProvider)) {
  277. ODataException::createInternalServerError(
  278. Messages::dataServiceInvalidQueryInstance()
  279. );
  280. }
  281. if ($expectingQP2) {
  282. if (array_search('ODataProducer\Providers\Query\IDataServiceQueryProvider2', class_implements($queryProvider)) === false) {
  283. ODataException::createInternalServerError(
  284. Messages::dataServiceInvalidQueryInstance()
  285. );
  286. }
  287. } else {
  288. if (array_search('ODataProducer\Providers\Query\IDataServiceQueryProvider', class_implements($queryProvider)) === false) {
  289. ODataException::createInternalServerError(
  290. Messages::dataServiceInvalidQueryInstance()
  291. );
  292. }
  293. }
  294. $this->_dataServiceConfiguration = new DataServiceConfiguration($metadataProvider);
  295. $this->_metadataQueryProviderWrapper = new MetadataQueryProviderWrapper(
  296. $metadataProvider,
  297. $queryProvider,
  298. $this->_dataServiceConfiguration,
  299. $expectingQP2
  300. );
  301. $this->initializeService($this->_dataServiceConfiguration);
  302. }
  303. /**
  304. * Serialize the requested resource.
  305. *
  306. * @param RequestDescription &$requestDescription The description of the request
  307. * submitted by the client.
  308. * @param UriProcessor &$uriProcessor Reference to the uri processor.
  309. *
  310. * @return void
  311. */
  312. protected function serializeResult(RequestDescription &$requestDescription,
  313. UriProcessor &$uriProcessor
  314. ) {
  315. $isETagHeaderAllowed = $requestDescription->isETagHeaderAllowed();
  316. if (!$isETagHeaderAllowed) {
  317. if (!is_null($this->_dataServiceHost->getRequestIfMatch())
  318. ||!is_null($this->_dataServiceHost->getRequestIfNoneMatch())
  319. ) {
  320. ODataException::createBadRequestError(
  321. Messages::dataServiceETagCannotBeSpecified(
  322. $this->getHost()->getAbsoluteRequestUri()->getUrlAsString()
  323. )
  324. );
  325. }
  326. }
  327. $responseContentType = null;
  328. $responseFormat
  329. = self::getResponseFormat(
  330. $requestDescription,
  331. $uriProcessor, $this, $responseContentType
  332. );
  333. $odataModelInstance = null;
  334. $hasResponseBody = true;
  335. // Execution required at this point if request target to any resource
  336. // other than
  337. // (1) media resource - For Media resource 'getResponseFormat' already
  338. // performed execution
  339. // (2) metadata - internal resource
  340. // (3) service directory - internal resource
  341. if ($requestDescription->needExecution()) {
  342. $uriProcessor->execute();
  343. $objectModelSerializer
  344. = new ObjectModelSerializer($this, $requestDescription);
  345. if (!$requestDescription->isSingleResult()) {
  346. // Code path for collection (feed or links)
  347. $entryObjects = $requestDescription->getTargetResult();
  348. self::assert(
  349. !is_null($entryObjects) && is_array($entryObjects),
  350. '!is_null($entryObjects) && is_array($entryObjects)'
  351. );
  352. // If related resource set is empty for an entry then we should
  353. // not throw error instead response must be empty feed or empty links
  354. if ($requestDescription->isLinkUri()) {
  355. $odataModelInstance
  356. = $objectModelSerializer->writeUrlElements($entryObjects);
  357. self::assert(
  358. $odataModelInstance instanceof \ODataProducer\ObjectModel\ODataURLCollection,
  359. '$odataModelInstance instanceof ODataURLCollection'
  360. );
  361. } else {
  362. $odataModelInstance = $objectModelSerializer->writeTopLevelElements($entryObjects);
  363. self::assert(
  364. $odataModelInstance instanceof \ODataProducer\ObjectModel\ODataFeed,
  365. '$odataModelInstance instanceof ODataFeed'
  366. );
  367. }
  368. } else {
  369. // Code path for entry, complex, bag, resource reference link,
  370. // primitive type or primitive value
  371. $result = $requestDescription->getTargetResult();
  372. $requestTargetKind = $requestDescription->getTargetKind();
  373. if ($requestDescription->isLinkUri()) {
  374. // In the query 'Orders(1245)/$links/Customer', the targetted
  375. // Customer might be null
  376. if (is_null($result)) {
  377. ODataException::createResourceNotFoundError(
  378. $requestDescription->getIdentifier()
  379. );
  380. }
  381. $odataModelInstance
  382. = $objectModelSerializer->writeUrlElement($result);
  383. } else if ($requestTargetKind == RequestTargetKind::RESOURCE) {
  384. if (!is_null($this->_dataServiceHost->getRequestIfMatch())
  385. && !is_null($this->_dataServiceHost->getRequestIfNoneMatch())
  386. ) {
  387. ODataException::createBadRequestError(
  388. Messages::dataServiceBothIfMatchAndIfNoneMatchHeaderSpecified()
  389. );
  390. }
  391. // handle entry resource
  392. $needToSerializeResponse = true;
  393. $targetResourceType = $requestDescription->getTargetResourceType();
  394. $eTag = $this->compareETag(
  395. $result,
  396. $targetResourceType,
  397. $needToSerializeResponse
  398. );
  399. if ($needToSerializeResponse) {
  400. if (is_null($result)) {
  401. // In the query 'Orders(1245)/Customer', the targetted
  402. // Customer might be null
  403. // set status code to 204 => 'No Content'
  404. $this->_dataServiceHost->setResponseStatusCode(
  405. HttpStatus::CODE_NOCONTENT
  406. );
  407. $hasResponseBody = false;
  408. } else {
  409. $odataModelInstance
  410. = $objectModelSerializer->writeTopLevelElement($result);
  411. }
  412. } else {
  413. // Resource is not modified so set status code
  414. // to 304 => 'Not Modified'
  415. $this->_dataServiceHost
  416. ->setResponseStatusCode(HttpStatus::CODE_NOT_MODIFIED);
  417. $hasResponseBody = false;
  418. }
  419. // if resource has eTagProperty then eTag header needs to written
  420. if (!is_null($eTag)) {
  421. $this->_dataServiceHost->setResponseETag($eTag);
  422. }
  423. } else if ($requestTargetKind == RequestTargetKind::COMPLEX_OBJECT) {
  424. $odataModelInstance = new ODataPropertyContent();
  425. $targetResourceTypeComplex = $requestDescription->getTargetResourceType();
  426. $objectModelSerializer->writeTopLevelComplexObject(
  427. $result,
  428. $requestDescription->getProjectedProperty()->getName(),
  429. $targetResourceTypeComplex,
  430. $odataModelInstance
  431. );
  432. } else if ($requestTargetKind == RequestTargetKind::BAG) {
  433. $odataModelInstance = new ODataPropertyContent();
  434. $targetResourceTypeBag = $requestDescription->getTargetResourceType();
  435. $objectModelSerializer->writeTopLevelBagObject(
  436. $result,
  437. $requestDescription->getProjectedProperty()->getName(),
  438. $targetResourceTypeBag,
  439. $odataModelInstance
  440. );
  441. } else if ($requestTargetKind == RequestTargetKind::PRIMITIVE) {
  442. $odataModelInstance = new ODataPropertyContent();
  443. $projectedProperty = $requestDescription->getProjectedProperty();
  444. $objectModelSerializer->writeTopLevelPrimitive(
  445. $result,
  446. $projectedProperty,
  447. $odataModelInstance
  448. );
  449. } else if ($requestTargetKind == RequestTargetKind::PRIMITIVE_VALUE) {
  450. // Code path for primitive value (Since its primitve no need for
  451. // object model serialization)
  452. // Customers('ANU')/CompanyName/$value => string
  453. // Employees(1)/Photo/$value => binary stream
  454. // Customers/$count => string
  455. } else {
  456. self::assert(false, 'Unexpected resource target kind');
  457. }
  458. }
  459. }
  460. //Note: Response content type can be null for named stream
  461. if ($hasResponseBody && !is_null($responseContentType)) {
  462. if ($responseFormat != ResponseFormat::BINARY) {
  463. $responseContentType .= ';charset=utf-8';
  464. }
  465. }
  466. if ($hasResponseBody) {
  467. ResponseWriter::write(
  468. $this,
  469. $requestDescription,
  470. $odataModelInstance,
  471. $responseContentType,
  472. $responseFormat
  473. );
  474. }
  475. }
  476. /**
  477. * Gets the response format for the requested resource.
  478. *
  479. * @param RequestDescription &$requestDescription The request submitted by
  480. * client and it's execution
  481. * result.
  482. * @param UriProcessor &$uriProcessor The reference to the
  483. * UriProcessor.
  484. * @param DataService &$dataService Reference to the data
  485. * service instance
  486. * @param string &$responseContentType On Return, this will hold
  487. * the response content-type, a null value means the requested resource
  488. * is named stream and IDSSP2::getStreamContentType returned null.
  489. *
  490. * @return ResponseFormat The format in which response needs to be serialized.
  491. *
  492. * @throws ODataException, HttpHeaderFailure
  493. */
  494. public static function getResponseFormat(RequestDescription &$requestDescription,
  495. UriProcessor &$uriProcessor, DataService &$dataService,
  496. &$responseContentType
  497. ) {
  498. // The Accept request-header field specifies media types which are
  499. // acceptable for the response
  500. $requestAcceptText = $dataService->getHost()->getRequestAccept();
  501. $responseFormat = ResponseFormat::UNSUPPORTED;
  502. $requestTargetKind = $requestDescription->getTargetKind();
  503. if ($requestDescription->isLinkUri()) {
  504. $requestTargetKind = RequestTargetKind::LINK;
  505. }
  506. if ($requestTargetKind == RequestTargetKind::METADATA) {
  507. $responseContentType = HttpProcessUtility::selectMimeType(
  508. $requestAcceptText,
  509. array(ODataConstants::MIME_APPLICATION_XML)
  510. );
  511. if (!is_null($responseContentType)) {
  512. $responseFormat = ResponseFormat::METADATA_DOCUMENT;
  513. }
  514. } else if ($requestTargetKind == RequestTargetKind::SERVICE_DIRECTORY) {
  515. $responseContentType = HttpProcessUtility::selectMimeType(
  516. $requestAcceptText,
  517. array(
  518. ODataConstants::MIME_APPLICATION_XML,
  519. ODataConstants::MIME_APPLICATION_ATOMSERVICE,
  520. ODataConstants::MIME_APPLICATION_JSON
  521. )
  522. );
  523. if (!is_null($responseContentType)) {
  524. $responseFormat = self::_getContentFormat($responseContentType);
  525. }
  526. } else if ($requestTargetKind == RequestTargetKind::PRIMITIVE_VALUE) {
  527. $supportedResponseMimeTypes = array(ODataConstants::MIME_TEXTPLAIN);
  528. $responseFormat = ResponseFormat::TEXT;
  529. if ($requestDescription->getIdentifier() != '$count') {
  530. $projectedProperty = $requestDescription->getProjectedProperty();
  531. self::assert(
  532. !is_null($projectedProperty),
  533. '!is_null($projectedProperty)'
  534. );
  535. $type = $projectedProperty->getInstanceType();
  536. self::assert(
  537. !is_null($type) && array_search(
  538. 'ODataProducer\Providers\Metadata\Type\IType',
  539. class_implements($type)
  540. ) !== false,
  541. '!is_null($type) && array_search(\'ODataProducer\Providers\Metadata\Type\IType\', class_implements($type)) !== false'
  542. );
  543. if ($type instanceof Binary) {
  544. $supportedResponseMimeTypes
  545. = array(ODataConstants::MIME_APPLICATION_OCTETSTREAM);
  546. $responseFormat = ResponseFormat::BINARY;
  547. }
  548. }
  549. $responseContentType = HttpProcessUtility::selectMimeType(
  550. $requestAcceptText,
  551. $supportedResponseMimeTypes
  552. );
  553. if (is_null($responseContentType)) {
  554. $responseFormat = ResponseFormat::UNSUPPORTED;
  555. }
  556. } else if ($requestTargetKind == RequestTargetKind::PRIMITIVE
  557. || $requestTargetKind == RequestTargetKind::COMPLEX_OBJECT
  558. || $requestTargetKind == RequestTargetKind::BAG
  559. || $requestTargetKind == RequestTargetKind::LINK
  560. ) {
  561. $responseContentType = HttpProcessUtility::selectMimeType(
  562. $requestAcceptText,
  563. array(
  564. ODataConstants::MIME_APPLICATION_XML,
  565. ODataConstants::MIME_TEXTXML,
  566. ODataConstants::MIME_APPLICATION_JSON
  567. )
  568. );
  569. if (!is_null($responseContentType)) {
  570. $responseFormat = self::_getContentFormat($responseContentType);
  571. }
  572. } else if ($requestTargetKind == RequestTargetKind::RESOURCE) {
  573. $responseContentType = HttpProcessUtility::selectMimeType(
  574. $requestAcceptText,
  575. array(
  576. ODataConstants::MIME_APPLICATION_ATOM,
  577. ODataConstants::MIME_APPLICATION_JSON
  578. )
  579. );
  580. if (!is_null($responseContentType)) {
  581. $responseFormat = self::_getContentFormat($responseContentType);
  582. }
  583. } else if ($requestTargetKind == RequestTargetKind::MEDIA_RESOURCE) {
  584. $responseFormat = ResponseFormat::BINARY;
  585. if ($requestDescription->isNamedStream()
  586. || $requestDescription->getTargetResourceType()->isMediaLinkEntry()
  587. ) {
  588. $streamInfo = $requestDescription->getResourceStreamInfo();
  589. //Execute the query as we need media resource instance for
  590. //further processing
  591. $uriProcessor->execute();
  592. $requestDescription->setExecuted();
  593. // DSSW::getStreamContentType can throw error in 2 cases
  594. // 1. If the required stream implementation not found
  595. // 2. If IDSSP::getStreamContentType returns NULL for MLE
  596. $contentType
  597. = $dataService->getStreamProvider()
  598. ->getStreamContentType(
  599. $requestDescription->getTargetResult(),
  600. $streamInfo
  601. );
  602. if (!is_null($contentType)) {
  603. $responseContentType = HttpProcessUtility::selectMimeType(
  604. $requestAcceptText,
  605. array($contentType)
  606. );
  607. if (is_null($responseContentType)) {
  608. $responseFormat = ResponseFormat::UNSUPPORTED;
  609. }
  610. } else {
  611. // For NamedStream StreamWrapper::getStreamContentType
  612. // can return NULL if the requested named stream has not
  613. // yet been uploaded. But for an MLE if
  614. // IDSSP::getStreamContentType
  615. // returns NULL then StreamWrapper will throw error
  616. $responseContentType = null;
  617. }
  618. } else {
  619. ODataException::createBadRequestError(
  620. Messages::badRequestInvalidUriForMediaResource(
  621. $dataService->getHost()->getAbsoluteRequestUri()->getUrlAsString()
  622. )
  623. );
  624. }
  625. }
  626. if ($responseFormat == ResponseFormat::UNSUPPORTED) {
  627. throw new ODataException(
  628. Messages::dataServiceExceptionUnsupportedMediaType(),
  629. 415
  630. );
  631. }
  632. return $responseFormat;
  633. }
  634. /**
  635. * Get the content format corresponding to the given mime type.
  636. *
  637. * @param string $mime mime type for the request.
  638. *
  639. * @return ResponseFormat Response format mapping to the given mime type.
  640. */
  641. private static function _getContentFormat($mime)
  642. {
  643. if (strcasecmp($mime, ODataConstants::MIME_APPLICATION_JSON) === 0) {
  644. return ResponseFormat::JSON;
  645. } else if (strcasecmp($mime, ODataConstants::MIME_APPLICATION_ATOM) === 0) {
  646. return ResponseFormat::ATOM;
  647. } else {
  648. $flag
  649. = strcasecmp($mime, ODataConstants::MIME_APPLICATION_XML) === 0 ||
  650. strcasecmp($mime, ODataConstants::MIME_APPLICATION_ATOMSERVICE) === 0 ||
  651. strcasecmp($mime, ODataConstants::MIME_TEXTXML) === 0;
  652. self::assert(
  653. $flag,
  654. 'expecting application/xml, application/atomsvc+xml or plain/xml, got ' . $mime
  655. );
  656. return ResponseFormat::PLAIN_XML;
  657. }
  658. }
  659. /**
  660. * For the given entry object compare it's eTag (if it has eTag properties)
  661. * with current eTag request headers (if it present).
  662. *
  663. * @param mixed &$entryObject entity resource for which etag
  664. * needs to be checked.
  665. * @param ResourceType &$resourceType Resource type of the entry
  666. * object.
  667. * @param boolean &$needToSerializeResponse On return, this will contain
  668. * True if response needs to be
  669. * serialized, False otherwise.
  670. *
  671. * @return string/NULL The ETag for the entry object if it has eTag properties
  672. * NULL otherwise.
  673. */
  674. protected function compareETag(&$entryObject, ResourceType &$resourceType,
  675. &$needToSerializeResponse
  676. ) {
  677. $needToSerializeResponse = true;
  678. $eTag = null;
  679. $ifMatch = $this->_dataServiceHost->getRequestIfMatch();
  680. $ifNoneMatch = $this->_dataServiceHost->getRequestIfNoneMatch();
  681. if (is_null($entryObject)) {
  682. if (!is_null($ifMatch)) {
  683. ODataException::createPreConditionFailedError(
  684. Messages::dataServiceETagNotAllowedForNonExistingResource()
  685. );
  686. }
  687. return null;
  688. }
  689. if (!$resourceType->hasETagProperties()) {
  690. if (!is_null($ifMatch) || !is_null($ifNoneMatch)) {
  691. // No eTag properties but request has eTag headers, bad request
  692. ODataException::createBadRequestError(
  693. Messages::dataServiceNoETagPropertiesForType()
  694. );
  695. }
  696. // We need write the response but no eTag header
  697. return null;
  698. }
  699. if (is_null($ifMatch) && is_null($ifNoneMatch)) {
  700. // No request eTag header, we need to write the response
  701. // and eTag header
  702. } else if (strcmp($ifMatch, '*') == 0) {
  703. // If-Match:* => we need to write the response and eTag header
  704. } else if (strcmp($ifNoneMatch, '*') == 0) {
  705. // if-None-Match:* => Do not write the response (304 not modified),
  706. // but write eTag header
  707. $needToSerializeResponse = false;
  708. } else {
  709. $eTag = $this->getETagForEntry($entryObject, $resourceType);
  710. // Note: The following code for attaching the prefix W\"
  711. // and the suffix " can be done in getETagForEntry function
  712. // but that is causing an issue in Linux env where the
  713. // firefix browser is unable to parse the ETag in this case.
  714. // Need to follow up PHP core devs for this.
  715. $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
  716. if (!is_null($ifMatch)) {
  717. if (strcmp($eTag, $ifMatch) != 0) {
  718. // Requested If-Match value does not match with current
  719. // eTag Value then pre-condition error
  720. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  721. ODataException::createPreConditionFailedError(
  722. Messages::dataServiceETagValueDoesNotMatch()
  723. );
  724. }
  725. } else if (strcmp($eTag, $ifNoneMatch) == 0) {
  726. //304 not modified, but in write eTag header
  727. $needToSerializeResponse = false;
  728. }
  729. }
  730. if (is_null($eTag)) {
  731. $eTag = $this->getETagForEntry($entryObject, $resourceType);
  732. // Note: The following code for attaching the prefix W\"
  733. // and the suffix " can be done in getETagForEntry function
  734. // but that is causing an issue in Linux env where the
  735. // firefix browser is unable to parse the ETag in this case.
  736. // Need to follow up PHP core devs for this.
  737. $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
  738. }
  739. return $eTag;
  740. }
  741. /**
  742. * Returns the etag for the given resource.
  743. * Note: This function will not add W\" prefix and " suffix, its callers
  744. * repsonsability.
  745. *
  746. * @param mixed &$entryObject Resource for which etag value needs to
  747. * be returned
  748. * @param ResourceType &$resourceType Resource type of the $entryObject
  749. *
  750. * @return string/NULL ETag value for the given resource (with values encoded
  751. * for use in a URI) there are etag properties, NULL if
  752. * there is no etag property.
  753. */
  754. protected function getETagForEntry(&$entryObject, ResourceType &$resourceType)
  755. {
  756. $eTag = null;
  757. $comma = null;
  758. foreach ($resourceType->getETagProperties() as $eTagProperty) {
  759. $type = $eTagProperty->getInstanceType();
  760. self::assert(
  761. !is_null($type)
  762. && array_search('ODataProducer\Providers\Metadata\Type\IType', class_implements($type)) !== false,
  763. '!is_null($type)
  764. && array_search(\'ODataProducer\Providers\Metadata\Type\IType\', class_implements($type)) !== false'
  765. );
  766. $value = null;
  767. try {
  768. $reflectionProperty
  769. = new \ReflectionProperty(
  770. $entryObject,
  771. $eTagProperty->getName()
  772. );
  773. $value = $reflectionProperty->getValue($entryObject);
  774. } catch (\ReflectionException $reflectionException) {
  775. throw ODataException::createInternalServerError(
  776. Messages::dataServiceFailedToAccessProperty(
  777. $eTagProperty->getName(),
  778. $resourceType->getName()
  779. )
  780. );
  781. }
  782. if (is_null($value)) {
  783. $eTag = $eTag . $comma. 'null';
  784. } else {
  785. $eTag = $eTag . $comma . $type->convertToOData($value);
  786. }
  787. $comma = ',';
  788. }
  789. if (!is_null($eTag)) {
  790. // If eTag is made up of datetime or string properties then the above
  791. // IType::converToOData will perform utf8 and url encode. But we don't
  792. // want this for eTag value.
  793. $eTag = urldecode(utf8_decode($eTag));
  794. return rtrim($eTag, ',');
  795. }
  796. return null;
  797. }
  798. /**
  799. * This function will perform the following operations:
  800. * (1) Invoke delegateRequestProcessing method to process the request based
  801. * on request method (GET, PUT/MERGE, POST, DELETE)
  802. * (3) If the result of processing of request needs to be serialized as HTTP
  803. * response body (e.g. GET request result in single resource or resource
  804. * collection, successful POST operation for an entity need inserted
  805. * entity to be serialized back etc..), Serialize the result by using
  806. * 'serializeReultForResponseBody' method
  807. * Set the serialized result to
  808. * WebOperationContext::Current()::OutgoingWebResponseContext::Stream.
  809. *
  810. * @return void
  811. */
  812. protected function handleRequest2()
  813. {
  814. }
  815. /**
  816. * This method will perform the following operations:
  817. * (1) If request method is GET, then result is already there in the
  818. * RequestDescription so simply return the RequestDescription
  819. * (2). If request method is for CDU
  820. * (Create/Delete/Update - POST/DELETE/PUT-MERGE) hand
  821. * over the responsibility to respective handlers. The handler
  822. * methods are:
  823. * (a) handlePOSTOperation() => POST
  824. * (b) handlePUTOperation() => PUT/MERGE
  825. * (c) handleDELETEOperation() => DELETE
  826. * (3). Check whether its required to write any result to the response
  827. * body
  828. * (a). Request method is GET
  829. * (b). Request is a POST for adding NEW Entry
  830. * (c). Request is a POST for adding Media Resource Stream
  831. * (d). Request is a POST for adding a link
  832. * (e). Request is a DELETE for deleting entry or relationship
  833. * (f). Request is a PUT/MERGE for updating an entry
  834. * (g). Request is a PUT for updating a link
  835. * In case a, b and c we need to write the result to response body,
  836. * for d, e, f and g no body content.
  837. *
  838. * @return RequestDescription/null Instance of RequestDescription with
  839. * result to be write back Null if no result to write.
  840. */
  841. protected function delegateRequestProcessing()
  842. {
  843. }
  844. /**
  845. * Serialize the result in the current request description using
  846. * appropriate odata writer (AtomODataWriter/JSONODataWriter)
  847. *
  848. * @return void
  849. *
  850. */
  851. protected function serializeReultForResponseBody()
  852. {
  853. }
  854. /**
  855. * Handle POST request.
  856. *
  857. * @return void
  858. *
  859. * @throws NotImplementedException
  860. */
  861. protected function handlePOSTOperation()
  862. {
  863. }
  864. /**
  865. * Handle PUT/MERGE request.
  866. *
  867. * @return void
  868. *
  869. * @throws NotImplementedException
  870. */
  871. protected function handlePUTOperation()
  872. {
  873. }
  874. /**
  875. * Handle DELETE request.
  876. *
  877. * @return void
  878. *
  879. * @throws NotImplementedException
  880. */
  881. protected function handleDELETEOperation()
  882. {
  883. }
  884. /**
  885. * Assert that the given condition is true.
  886. *
  887. * @param boolean $condition The condtion to check.
  888. * @param string $conditionAsString Message to show if assertion fails.
  889. *
  890. * @return void
  891. *
  892. * @throws InvalidOperationException
  893. */
  894. protected static function assert($condition, $conditionAsString)
  895. {
  896. if (!$condition) {
  897. throw new InvalidOperationException(
  898. "Unexpected state, expecting $conditionAsString"
  899. );
  900. }
  901. }
  902. }
  903. ?>