PageRenderTime 59ms CodeModel.GetById 15ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 0ms

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