PageRenderTime 63ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

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

#
PHP | 766 lines | 504 code | 49 blank | 213 comment | 102 complexity | fce8487035cf496899a373d6aac0c947 MD5 | raw file
  1. <?php
  2. /**
  3. * A type to process client's requets URI
  4. * The syntax of request URI is:
  5. * Scheme Host Port ServiceRoot ResourcePath ? QueryOption
  6. * For more details refer:
  7. * http://www.odata.org/developers/protocols/uri-conventions#UriComponents
  8. *
  9. * PHP version 5.3
  10. *
  11. * @category ODataProducer
  12. * @package ODataProducer_UriProcessor
  13. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  14. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  15. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  16. * @version SVN: 1.0
  17. * @link http://odataphpproducer.codeplex.com
  18. *
  19. */
  20. namespace ODataProducer\UriProcessor;
  21. use ODataProducer\Providers\MetadataQueryProviderWrapper;
  22. use ODataProducer\Providers\Metadata\ResourcePropertyKind;
  23. use ODataProducer\Providers\Metadata\ResourceTypeKind;
  24. use ODataProducer\Providers\Metadata\ResourceSetWrapper;
  25. use ODataProducer\Providers\Metadata\ResourceProperty;
  26. use ODataProducer\UriProcessor\QueryProcessor\QueryProcessor;
  27. use ODataProducer\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
  28. use ODataProducer\UriProcessor\ResourcePathProcessor\ResourcePathProcessor;
  29. use ODataProducer\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor;
  30. use ODataProducer\UriProcessor\ResourcePathProcessor\SegmentParser\RequestTargetKind;
  31. use ODataProducer\UriProcessor\ResourcePathProcessor\SegmentParser\RequestTargetSource;
  32. use ODataProducer\DataService;
  33. use ODataProducer\Common\Url;
  34. use ODataProducer\Common\Messages;
  35. use ODataProducer\Common\ODataException;
  36. use ODataProducer\Common\NotImplementedException;
  37. use ODataProducer\Common\InvalidOperationException;
  38. use ODataProducer\Common\ODataConstants;
  39. /**
  40. * OData request uri processor.
  41. *
  42. * @category ODataProducer
  43. * @package ODataProducer_UriProcessor
  44. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  45. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  46. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  47. * @version Release: 1.0
  48. * @link http://odataphpproducer.codeplex.com
  49. */
  50. class UriProcessor
  51. {
  52. /**
  53. * Description of the OData request that a client has submitted.
  54. *
  55. * @var RequestDescription
  56. */
  57. private $_requestDescription;
  58. /**
  59. * Holds reference to the data service instance.
  60. *
  61. * @var DataService
  62. */
  63. private $_dataService;
  64. /**
  65. * Holds reference to the wrapper over IDSMP and IDSQP implementation.
  66. *
  67. * @var MetadataQueryProviderWrapper
  68. */
  69. private $_provider;
  70. /**
  71. * Collection of segment names.
  72. *
  73. * @var array(string)
  74. */
  75. private $_segmentNames;
  76. /**
  77. * Collection of segment ResourceSetWrapper instances.
  78. *
  79. * @var array(ResourceSetWrapper)
  80. */
  81. private $_segmentResourceSetWrappers;
  82. /**
  83. * Constructs a new instance of UriProcessor
  84. *
  85. * @param DataService &$dataService Reference to the data service instance.
  86. */
  87. private function __construct(DataService &$dataService)
  88. {
  89. $this->_dataService = $dataService;
  90. $this->_provider = $dataService->getMetadataQueryProviderWrapper();
  91. $this->_segmentNames = array();
  92. $this->_segmentResourceSetWrappers = array();
  93. }
  94. /**
  95. * Process the resource path and query options of client's request uri.
  96. *
  97. * @param DataService &$dataService Reference to the data service instance.
  98. *
  99. * @return void
  100. *
  101. * @throws ODataException
  102. */
  103. public static function process(DataService &$dataService)
  104. {
  105. $absoluteRequestUri = $dataService->getHost()->getAbsoluteRequestUri();
  106. $absoluteServiceUri = $dataService->getHost()->getAbsoluteServiceUri();
  107. if (!$absoluteServiceUri->isBaseOf($absoluteRequestUri)) {
  108. ODataException::createInternalServerError(
  109. Messages::uriProcessorRequestUriDoesNotHaveTheRightBaseUri(
  110. $absoluteRequestUri->getUrlAsString(),
  111. $absoluteServiceUri->getUrlAsString()
  112. )
  113. );
  114. }
  115. $uriProcessor = new UriProcessor($dataService);
  116. //Parse the resource path part of the request Uri.
  117. try {
  118. $uriProcessor->_requestDescription
  119. = ResourcePathProcessor::process(
  120. $absoluteRequestUri,
  121. $dataService
  122. );
  123. } catch (ODataException $odataException) {
  124. throw $odataException;
  125. }
  126. //Parse the query string options of the request Uri.
  127. try {
  128. QueryProcessor::process(
  129. $uriProcessor->_requestDescription,
  130. $dataService
  131. );
  132. } catch (ODataException $odataException) {
  133. throw $odataException;
  134. }
  135. return $uriProcessor;
  136. }
  137. /**
  138. * Gets reference to the request submitted by client.
  139. *
  140. * @return RequestDescription
  141. */
  142. public function getRequestDescription()
  143. {
  144. return $this->_requestDescription;
  145. }
  146. /**
  147. * Execute the client submitted request aganist the data source.
  148. *
  149. * @return void
  150. */
  151. public function execute()
  152. {
  153. $segmentDescriptors = &$this->_requestDescription->getSegmentDescriptors();
  154. foreach ($segmentDescriptors as $segmentDescriptor) {
  155. $requestTargetKind = $segmentDescriptor->getTargetKind();
  156. if ($segmentDescriptor->getTargetSource() == RequestTargetSource::ENTITY_SET) {
  157. $this->_handleSegmentTargetsToResourceSet($segmentDescriptor);
  158. } else if ($requestTargetKind == RequestTargetKind::RESOURCE) {
  159. if (is_null($segmentDescriptor->getPrevious()->getResult())) {
  160. ODataException::createResourceNotFoundError(
  161. $segmentDescriptor->getPrevious()->getIdentifier()
  162. );
  163. }
  164. $this->_handleSegmentTargetsToRelatedResource($segmentDescriptor);
  165. } else if ($requestTargetKind == RequestTargetKind::LINK) {
  166. $segmentDescriptor->setResult($segmentDescriptor->getPrevious()->getResult());
  167. } else if ($segmentDescriptor->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) {
  168. // we are done, $count will the last segment and
  169. // taken care by _applyQueryOptions method
  170. $segmentDescriptor->setResult($this->_requestDescription->getCountValue());
  171. break;
  172. } else {
  173. if ($requestTargetKind == RequestTargetKind::MEDIA_RESOURCE) {
  174. if (is_null($segmentDescriptor->getPrevious()->getResult())) {
  175. ODataException::createResourceNotFoundError(
  176. $segmentDescriptor->getPrevious()->getIdentifier()
  177. );
  178. }
  179. // For MLE and Named Stream the result of last segment
  180. // should be that of previous segment, this is required
  181. // while retriving content type or stream from IDSSP
  182. $segmentDescriptor->setResult($segmentDescriptor->getPrevious()->getResult());
  183. // we are done, as named stream property or $value on
  184. // media resource will be the last segment
  185. break;
  186. } else {
  187. $value = $segmentDescriptor->getPrevious()->getResult();
  188. while (!is_null($segmentDescriptor)) {
  189. if (is_null($value)) {
  190. $value = null;
  191. } else {
  192. try {
  193. $property = new \ReflectionProperty($value, $segmentDescriptor->getIdentifier());
  194. $value = $property->getValue($value);
  195. } catch (\ReflectionException $reflectionException) {
  196. //throw ODataException::createInternalServerError(Messages::orderByParserFailedToAccessOrInitializeProperty($resourceProperty->getName(), $resourceType->getName()));
  197. }
  198. }
  199. $segmentDescriptor->setResult($value);
  200. $segmentDescriptor = $segmentDescriptor->getNext();
  201. if (!is_null($segmentDescriptor)
  202. && $segmentDescriptor->getIdentifier() == ODataConstants::URI_VALUE_SEGMENT
  203. ) {
  204. $segmentDescriptor->setResult($value);
  205. $segmentDescriptor = $segmentDescriptor->getNext();
  206. }
  207. }
  208. //done, exit from outer loop as inner while complete traversal.
  209. break;
  210. }
  211. }
  212. if (is_null($segmentDescriptor->getNext())
  213. || $segmentDescriptor->getNext()->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT
  214. ) {
  215. $this->_applyQueryOptions($segmentDescriptor);
  216. }
  217. }
  218. $this->_handleExpansion();
  219. }
  220. /**
  221. * Query for a resource set pointed by the given segment descriptor and update
  222. * the descriptor with the result.
  223. *
  224. * @param SegmentDescriptor &$segmentDescriptor Describes the resource set to
  225. * query.
  226. *
  227. * @return void
  228. */
  229. private function _handleSegmentTargetsToResourceSet(
  230. SegmentDescriptor &$segmentDescriptor
  231. ) {
  232. if ($segmentDescriptor->isSingleResult()) {
  233. $entityInstance = $this->_provider->getResourceFromResourceSet(
  234. $segmentDescriptor->getTargetResourceSetWrapper()->getResourceSet(),
  235. $segmentDescriptor->getKeyDescriptor()
  236. );
  237. $segmentDescriptor->setResult($entityInstance);
  238. } else {
  239. $entityInstances = $this->_provider->getResourceSet(
  240. $segmentDescriptor->getTargetResourceSetWrapper()->getResourceSet(),
  241. $this->_requestDescription->getInternalFilterInfo()
  242. );
  243. $segmentDescriptor->setResult($entityInstances);
  244. }
  245. }
  246. /**
  247. * Query for a related resource set or resource set reference pointed by the
  248. * given segment descriptor and update the descriptor with the result.
  249. *
  250. * @param SegmentDescriptor &$segmentDescriptor Describes the related resource
  251. * to query.
  252. *
  253. * @return void
  254. */
  255. private function _handleSegmentTargetsToRelatedResource(
  256. SegmentDescriptor &$segmentDescriptor
  257. ) {
  258. $projectedProperty = $segmentDescriptor->getProjectedProperty();
  259. $projectedPropertyKind = $projectedProperty->getKind();
  260. if ($projectedPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE) {
  261. if ($segmentDescriptor->isSingleResult()) {
  262. $entityInstance
  263. = $this->_provider->getResourceFromRelatedResourceSet(
  264. $segmentDescriptor->getPrevious()->getTargetResourceSetWrapper()->getResourceSet(),
  265. $segmentDescriptor->getPrevious()->getResult(),
  266. $segmentDescriptor->getTargetResourceSetWrapper()->getResourceSet(),
  267. $projectedProperty,
  268. $segmentDescriptor->getKeyDescriptor()
  269. );
  270. $segmentDescriptor->setResult($entityInstance);
  271. } else {
  272. $entityInstances
  273. = $this->_provider->getRelatedResourceSet(
  274. $segmentDescriptor->getPrevious()->getTargetResourceSetWrapper()->getResourceSet(),
  275. $segmentDescriptor->getPrevious()->getResult(),
  276. $segmentDescriptor->getTargetResourceSetWrapper()->getResourceSet(),
  277. $segmentDescriptor->getProjectedProperty(),
  278. $this->_requestDescription->getInternalFilterInfo()
  279. );
  280. $segmentDescriptor->setResult($entityInstances);
  281. }
  282. } else if ($projectedPropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE) {
  283. $entityInstance
  284. = $this->_provider->getRelatedResourceReference(
  285. $segmentDescriptor->getPrevious()->getTargetResourceSetWrapper()->getResourceSet(),
  286. $segmentDescriptor->getPrevious()->getResult(),
  287. $segmentDescriptor->getTargetResourceSetWrapper()->getResourceSet(),
  288. $segmentDescriptor->getProjectedProperty()
  289. );
  290. $segmentDescriptor->setResult($entityInstance);
  291. } else {
  292. //Unexpected state
  293. }
  294. }
  295. /**
  296. * Applies the query options to the resource(s) retrieved from the data source.
  297. *
  298. * @param SegmentDescriptor &$segmentDescriptor The descriptor which holds
  299. * resource(s) on which query
  300. * options to be applied.
  301. *
  302. * @return void
  303. */
  304. private function _applyQueryOptions(SegmentDescriptor &$segmentDescriptor)
  305. {
  306. $result = $segmentDescriptor->getResult();
  307. //Apply $filter option
  308. if (!is_null($result)) {
  309. $internalFilterInfo
  310. = $this->_requestDescription->getInternalFilterInfo();
  311. if (!is_null($internalFilterInfo)) {
  312. if (!$internalFilterInfo->isCustomExpression()) {
  313. // The QP implementation is not going to perform the filtering
  314. // opted for PHPExpressionProvider so run the filtering.
  315. $filterFunction
  316. = $internalFilterInfo->getFilterFunction()->getReference();
  317. if (is_array($result)) {
  318. $count = count($result);
  319. for ($i = 0; $i < $count; $i++) {
  320. if (!$filterFunction($result[$i])) {
  321. unset($result[$i]);
  322. }
  323. }
  324. $result = array_merge($result);
  325. } else {
  326. if (!$filterFunction($result)) {
  327. unset($result);
  328. $result = null;
  329. }
  330. }
  331. unset($filterFunction);
  332. } else {
  333. // The QP2 implementation performed the filtering so don't perform
  334. // filtering using library generated filter function.
  335. }
  336. unset($internalFilterInfo);
  337. }
  338. }
  339. // $inlinecount=allpages should ignore the query options
  340. // $skiptoken, $top and $skip so take count before applying these options
  341. if ($this->_requestDescription->getRequestCountOption() != RequestCountOption::NONE && is_array($result)
  342. ) {
  343. $this->_requestDescription->setCountValue(count($result));
  344. }
  345. $applicableForSetQuery = is_array($result) && !empty($result);
  346. if ($applicableForSetQuery) {
  347. //Apply (implicit and explicit) $orderby option
  348. $internalOrderByInfo
  349. = $this->_requestDescription->getInternalOrderByInfo();
  350. if (!is_null($internalOrderByInfo)) {
  351. $orderByFunction
  352. = $internalOrderByInfo->getSorterFunction()->getReference();
  353. usort($result, $orderByFunction);
  354. }
  355. //Apply $skiptoken option
  356. $internalSkipTokenInfo
  357. = $this->_requestDescription->getInternalSkipTokenInfo();
  358. if (!is_null($internalSkipTokenInfo)) {
  359. $matchingIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($result);
  360. $result = array_slice($result, $matchingIndex);
  361. }
  362. //Apply $top and $skip option
  363. if (!empty($result)) {
  364. $top = $this->_requestDescription->getTopCount();
  365. $skip = $this->_requestDescription->getSkipCount();
  366. if (!is_null($top) && !is_null($skip)) {
  367. $result = array_slice($result, $skip, $top);
  368. } else if (is_null($top)) {
  369. $result = array_slice($result, $skip);
  370. } else if (is_null($skip)) {
  371. $result = array_slice($result, 0, $top);
  372. }
  373. //$skip and $top affects $count so consider here.
  374. if ($this->_requestDescription->getRequestCountOption() == RequestCountOption::VALUE_ONLY) {
  375. $this->_requestDescription->setCountValue(count($result));
  376. }
  377. }
  378. }
  379. $segmentDescriptor->setResult($result);
  380. }
  381. /**
  382. * Perfrom expansion.
  383. *
  384. * @return void
  385. */
  386. private function _handleExpansion()
  387. {
  388. $rootrojectionNode = $this->_requestDescription->getRootProjectionNode();
  389. if (!is_null($rootrojectionNode)
  390. && $rootrojectionNode->isExpansionSpecified()
  391. ) {
  392. $result = $this->_requestDescription->getTargetResult();
  393. if (!is_null($result) || is_array($result) && !empty($result)) {
  394. $needPop = $this->_pushSegmentForRoot();
  395. $this->_executeExpansion($result);
  396. $this->_popSegment($needPop);
  397. }
  398. }
  399. }
  400. /**
  401. * Execute queries for expansion.
  402. *
  403. * @param array(mixed)/mixed &$result Resource(s) whose navigation properties
  404. * needs to be expanded.
  405. *
  406. * @return void
  407. */
  408. private function _executeExpansion(&$result)
  409. {
  410. $expandedProjectionNodes = $this->_getExpandedProjectionNodes();
  411. foreach ($expandedProjectionNodes as $expandedProjectionNode) {
  412. $isCollection
  413. = $expandedProjectionNode->getResourceProperty()->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE;
  414. $expandedPropertyName
  415. = $expandedProjectionNode->getResourceProperty()->getName();
  416. if (is_array($result)) {
  417. foreach ($result as $entry) {
  418. // Check for null entry
  419. if ($isCollection) {
  420. $currentResourceSet = $this->_getCurrentResourceSetWrapper()->getResourceSet();
  421. $resourceSetOfProjectedProperty = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
  422. $projectedProperty1 = $expandedProjectionNode->getResourceProperty();
  423. $result1 = $this->_provider->getRelatedResourceSet(
  424. $currentResourceSet,
  425. $entry,
  426. $resourceSetOfProjectedProperty,
  427. $projectedProperty1,
  428. null
  429. );
  430. if (!empty($result1)) {
  431. $internalOrderByInfo
  432. = $expandedProjectionNode->getInternalOrderByInfo();
  433. if (!is_null($internalOrderByInfo)) {
  434. $orderByFunction
  435. = $internalOrderByInfo->getSorterFunction()->getReference();
  436. usort($result1, $orderByFunction);
  437. unset($internalOrderByInfo);
  438. $takeCount = $expandedProjectionNode->getTakeCount();
  439. if (!is_null($takeCount)) {
  440. $result1 = array_slice($result1, 0, $takeCount);
  441. }
  442. }
  443. $entry->$expandedPropertyName = $result1;
  444. $projectedProperty = $expandedProjectionNode->getResourceProperty();
  445. $needPop = $this->_pushSegmentForNavigationProperty(
  446. $projectedProperty
  447. );
  448. $this->_executeExpansion($result1);
  449. $this->_popSegment($needPop);
  450. } else {
  451. $entry->$expandedPropertyName = array();
  452. }
  453. } else {
  454. $currentResourceSet1 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
  455. $resourceSetOfProjectedProperty1 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
  456. $projectedProperty2 = $expandedProjectionNode->getResourceProperty();
  457. $result1 = $this->_provider->getRelatedResourceReference(
  458. $currentResourceSet1,
  459. $entry,
  460. $resourceSetOfProjectedProperty1,
  461. $projectedProperty2
  462. );
  463. $entry->$expandedPropertyName = $result1;
  464. if (!is_null($result1)) {
  465. $projectedProperty3 = $expandedProjectionNode->getResourceProperty();
  466. $needPop = $this->_pushSegmentForNavigationProperty(
  467. $projectedProperty3
  468. );
  469. $this->_executeExpansion($result1);
  470. $this->_popSegment($needPop);
  471. }
  472. }
  473. }
  474. } else {
  475. if ($isCollection) {
  476. $currentResourceSet2 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
  477. $resourceSetOfProjectedProperty2 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
  478. $projectedProperty4 = $expandedProjectionNode->getResourceProperty();
  479. $result1 = $this->_provider->getRelatedResourceSet(
  480. $currentResourceSet2,
  481. $result,
  482. $resourceSetOfProjectedProperty2,
  483. $projectedProperty4,
  484. null
  485. );
  486. if (!empty($result1)) {
  487. $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo();
  488. if (!is_null($internalOrderByInfo)) {
  489. $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
  490. usort($result1, $orderByFunction);
  491. unset($internalOrderByInfo);
  492. $takeCount = $expandedProjectionNode->getTakeCount();
  493. if (!is_null($takeCount)) {
  494. $result1 = array_slice($result1, 0, $takeCount);
  495. }
  496. }
  497. $result->$expandedPropertyName = $result1;
  498. $projectedProperty7 = $expandedProjectionNode->getResourceProperty();
  499. $needPop = $this->_pushSegmentForNavigationProperty(
  500. $projectedProperty7
  501. );
  502. $this->_executeExpansion($result1);
  503. $this->_popSegment($needPop);
  504. } else {
  505. $result->$expandedPropertyName = array();
  506. }
  507. } else {
  508. $currentResourceSet3 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
  509. $resourceSetOfProjectedProperty3 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
  510. $projectedProperty5 = $expandedProjectionNode->getResourceProperty();
  511. $result1 = $this->_provider->getRelatedResourceReference(
  512. $currentResourceSet3,
  513. $result,
  514. $resourceSetOfProjectedProperty3,
  515. $projectedProperty5
  516. );
  517. $result->$expandedPropertyName = $result1;
  518. if (!is_null($result1)) {
  519. $projectedProperty6 = $expandedProjectionNode->getResourceProperty();
  520. $needPop = $this->_pushSegmentForNavigationProperty(
  521. $projectedProperty6
  522. );
  523. $this->_executeExpansion($result1);
  524. $this->_popSegment($needPop);
  525. }
  526. }
  527. }
  528. }
  529. }
  530. /**
  531. * Resource set wrapper for the resource being retireved.
  532. *
  533. * @return ResourceSetWrapper
  534. */
  535. private function _getCurrentResourceSetWrapper()
  536. {
  537. $count = count($this->_segmentResourceSetWrappers);
  538. if ($count == 0) {
  539. return $this->_requestDescription->getTargetResourceSetWrapper();
  540. } else {
  541. return $this->_segmentResourceSetWrappers[$count - 1];
  542. }
  543. }
  544. /**
  545. * Pushes a segment for the root of the tree
  546. * Note: Calls to this method should be balanced with calls to popSegment.
  547. *
  548. * @return true if the segment was pushed, false otherwise.
  549. */
  550. private function _pushSegmentForRoot()
  551. {
  552. $segmentName = $this->_requestDescription->getContainerName();
  553. $segmentResourceSetWrapper
  554. = $this->_requestDescription->getTargetResourceSetWrapper();
  555. return $this->_pushSegment($segmentName, $segmentResourceSetWrapper);
  556. }
  557. /**
  558. * Pushes a segment for the current navigation property being written out.
  559. * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
  560. * 'Segment Stack' and this method.
  561. * Note: Calls to this method should be balanced with calls to popSegment.
  562. *
  563. * @param ResourceProperty &$resourceProperty Current navigation property
  564. * being written out
  565. *
  566. * @return true if a segment was pushed, false otherwise
  567. *
  568. * @throws InvalidOperationException If this function invoked with non-navigation
  569. * property instance.
  570. */
  571. private function _pushSegmentForNavigationProperty(ResourceProperty &$resourceProperty)
  572. {
  573. if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) {
  574. $this->assert(
  575. !empty($this->_segmentNames),
  576. '!is_empty($this->_segmentNames'
  577. );
  578. $currentResourceSetWrapper = $this->_getCurrentResourceSetWrapper();
  579. $currentResourceType = $currentResourceSetWrapper->getResourceType();
  580. $currentResourceSetWrapper = $this->_dataService
  581. ->getMetadataQueryProviderWrapper()
  582. ->getResourceSetWrapperForNavigationProperty(
  583. $currentResourceSetWrapper,
  584. $currentResourceType,
  585. $resourceProperty
  586. );
  587. $this->assert(
  588. !is_null($currentResourceSetWrapper),
  589. '!null($currentResourceSetWrapper)'
  590. );
  591. return $this->_pushSegment(
  592. $resourceProperty->getName(),
  593. $currentResourceSetWrapper
  594. );
  595. } else {
  596. throw new InvalidOperationException(
  597. 'pushSegmentForNavigationProperty should not be called with non-entity type'
  598. );
  599. }
  600. }
  601. /**
  602. * Gets collection of expanded projection nodes under the current node.
  603. *
  604. * @return array(ExpandedProjectionNode) List of nodes
  605. * describing expansions for the current segment
  606. */
  607. private function _getExpandedProjectionNodes()
  608. {
  609. $expandedProjectionNode = $this->_getCurrentExpandedProjectionNode();
  610. $expandedProjectionNodes = array();
  611. if (!is_null($expandedProjectionNode)) {
  612. foreach ($expandedProjectionNode->getChildNodes() as $node) {
  613. if ($node instanceof ExpandedProjectionNode) {
  614. $expandedProjectionNodes[] = $node;
  615. }
  616. }
  617. }
  618. return $expandedProjectionNodes;
  619. }
  620. /**
  621. * Find a 'ExpandedProjectionNode' instance in the projection tree
  622. * which describes the current segment.
  623. *
  624. * @return ExpandedProjectionNode/NULL
  625. */
  626. private function _getCurrentExpandedProjectionNode()
  627. {
  628. $expandedProjectionNode
  629. = $this->_requestDescription->getRootProjectionNode();
  630. if (!is_null($expandedProjectionNode)) {
  631. $depth = count($this->_segmentNames);
  632. if ($depth != 0) {
  633. for ($i = 1; $i < $depth; $i++) {
  634. $expandedProjectionNode
  635. = $expandedProjectionNode->findNode($this->_segmentNames[$i]);
  636. $this->assert(
  637. !is_null($expandedProjectionNode),
  638. '!is_null($expandedProjectionNode)'
  639. );
  640. $this->assert(
  641. $expandedProjectionNode instanceof ExpandedProjectionNode,
  642. '$expandedProjectionNode instanceof ExpandedProjectionNode'
  643. );
  644. }
  645. }
  646. }
  647. return $expandedProjectionNode;
  648. }
  649. /**
  650. * Pushes information about the segment whose instance is going to be
  651. * retrieved from the IDSQP implementation
  652. * Note: Calls to this method should be balanced with calls to popSegment.
  653. *
  654. * @param string $segmentName Name of segment to push.
  655. * @param ResourceSetWrapper &$resourceSetWrapper The resource set wrapper
  656. * to push.
  657. *
  658. * @return true if the segment was push, false otherwise
  659. */
  660. private function _pushSegment($segmentName, ResourceSetWrapper &$resourceSetWrapper)
  661. {
  662. $rootProjectionNode = $this->_requestDescription->getRootProjectionNode();
  663. if (!is_null($rootProjectionNode)
  664. && $rootProjectionNode->isExpansionSpecified()
  665. ) {
  666. array_push($this->_segmentNames, $segmentName);
  667. array_push($this->_segmentResourceSetWrappers, $resourceSetWrapper);
  668. return true;
  669. }
  670. return false;
  671. }
  672. /**
  673. * Pops segment information from the 'Segment Stack'
  674. * Note: Calls to this method should be balanced with previous calls
  675. * to _pushSegment.
  676. *
  677. * @param boolean $needPop Is a pop required. Only true if last push
  678. * was successful.
  679. *
  680. * @return void
  681. *
  682. * @throws InvalidOperationException If found un-balanced call
  683. * with _pushSegment
  684. */
  685. private function _popSegment($needPop)
  686. {
  687. if ($needPop) {
  688. if (!empty($this->_segmentNames)) {
  689. array_pop($this->_segmentNames);
  690. array_pop($this->_segmentResourceSetWrappers);
  691. } else {
  692. throw new InvalidOperationException(
  693. 'Found non-balanced call to _pushSegment and popSegment'
  694. );
  695. }
  696. }
  697. }
  698. /**
  699. * Assert that the given condition is true.
  700. *
  701. * @param boolean $condition Constion to assert.
  702. * @param string $conditionAsString Message to show incase assertion fails.
  703. *
  704. * @return void
  705. *
  706. * @throws InvalidOperationException
  707. */
  708. protected function assert($condition, $conditionAsString)
  709. {
  710. if (!$condition) {
  711. throw new InvalidOperationException(
  712. "Unexpected state, expecting $conditionAsString"
  713. );
  714. }
  715. }
  716. }
  717. ?>