/** * Write in specific format * * @param IService $service * @param RequestDescription $request the OData request * @param mixed $entityModel OData model instance * @param String $responseContentType Content type of the response * */ public static function write(IService $service, RequestDescription $request, $entityModel, $responseContentType) { $responseBody = null; $dataServiceVersion = $request->getResponseVersion(); $targetKind = $request->getTargetKind(); if ($targetKind == TargetKind::METADATA()) { // /$metadata $writer = new MetadataWriter($service->getProvidersWrapper()); $responseBody = $writer->writeMetadata(); $dataServiceVersion = $writer->getDataServiceVersion(); } else { if ($targetKind == TargetKind::PRIMITIVE_VALUE() && $responseContentType != MimeTypes::MIME_APPLICATION_OCTETSTREAM) { //This second part is to exclude binary properties // /Customer('ALFKI')/CompanyName/$value // /Customers/$count $responseBody = utf8_encode($request->getTargetResult()); } else { if ($responseContentType == MimeTypes::MIME_APPLICATION_OCTETSTREAM || $targetKind == TargetKind::MEDIA_RESOURCE()) { // Binary property or media resource if ($request->getTargetKind() == TargetKind::MEDIA_RESOURCE()) { $result = $request->getTargetResult(); $streamInfo = $request->getResourceStreamInfo(); $provider = $service->getStreamProviderWrapper(); $eTag = $provider->getStreamETag($result, $streamInfo); $service->getHost()->setResponseETag($eTag); $responseBody = $provider->getReadStream($result, $streamInfo); } else { $responseBody = $request->getTargetResult(); } if (is_null($responseContentType)) { $responseContentType = MimeTypes::MIME_APPLICATION_OCTETSTREAM; } } else { $writer = $service->getODataWriterRegistry()->getWriter($request->getResponseVersion(), $responseContentType); //TODO: move ot Messages if (is_null($writer)) { throw new \Exception("no writer can handle the request"); } if (is_null($entityModel)) { //TODO: this seems like a weird way to know that the request is for a service document..i'd think we know this some other way $responseBody = $writer->writeServiceDocument($service->getProvidersWrapper())->getOutput(); } else { $responseBody = $writer->write($entityModel)->getOutput(); } } } } $service->getHost()->setResponseStatusCode(HttpStatus::CODE_OK); $service->getHost()->setResponseContentType($responseContentType); $service->getHost()->setResponseVersion($dataServiceVersion->toString() . ';'); $service->getHost()->setResponseCacheControl(ODataConstants::HTTPRESPONSE_HEADER_CACHECONTROL_NOCACHE); $service->getHost()->getOperationContext()->outgoingResponse()->setStream($responseBody); }
/** * Process the request Uri and creates an instance of * RequestDescription from the processed uri. * * @param IService $service Reference to the data service instance. * * @return RequestDescription * * @throws ODataException If any exception occurs while processing the segments * or in case of any version incompatibility. */ public static function process(IService $service) { $host = $service->getHost(); $absoluteRequestUri = $host->getAbsoluteRequestUri(); $absoluteServiceUri = $host->getAbsoluteServiceUri(); $requestUriSegments = array_slice($absoluteRequestUri->getSegments(), $absoluteServiceUri->getSegmentCount()); $segments = SegmentParser::parseRequestUriSegments($requestUriSegments, $service->getProvidersWrapper(), true); $request = new RequestDescription($segments, $absoluteRequestUri, $service->getConfiguration()->getMaxDataServiceVersion(), $host->getRequestVersion(), $host->getRequestMaxVersion()); $kind = $request->getTargetKind(); if ($kind == TargetKind::METADATA() || $kind == TargetKind::BATCH() || $kind == TargetKind::SERVICE_DIRECTORY()) { return $request; } if ($kind == TargetKind::PRIMITIVE_VALUE() || $kind == TargetKind::MEDIA_RESOURCE()) { // http://odata/NW.svc/Orders/$count // http://odata/NW.svc/Orders(123)/Customer/CustomerID/$value // http://odata/NW.svc/Employees(1)/$value // http://odata/NW.svc/Employees(1)/ThumbNail_48X48/$value $request->setContainerName($segments[count($segments) - 2]->getIdentifier()); } else { $request->setContainerName($request->getIdentifier()); } if ($request->getIdentifier() === ODataConstants::URI_COUNT_SEGMENT) { if (!$service->getConfiguration()->getAcceptCountRequests()) { throw ODataException::createBadRequestError(Messages::configurationCountNotAccepted()); } $request->queryType = QueryType::COUNT(); // use of $count requires request DataServiceVersion // and MaxDataServiceVersion greater than or equal to 2.0 $request->raiseResponseVersion(2, 0); $request->raiseMinVersionRequirement(2, 0); } else { if ($request->isNamedStream()) { $request->raiseMinVersionRequirement(3, 0); } else { if ($request->getTargetKind() == TargetKind::RESOURCE()) { if (!$request->isLinkUri()) { $resourceSetWrapper = $request->getTargetResourceSetWrapper(); //assert($resourceSetWrapper != null) $hasNamedStream = $resourceSetWrapper->hasNamedStreams($service->getProvidersWrapper()); $hasBagProperty = $resourceSetWrapper->hasBagProperty($service->getProvidersWrapper()); if ($hasNamedStream || $hasBagProperty) { $request->raiseResponseVersion(3, 0); } } } else { if ($request->getTargetKind() == TargetKind::BAG()) { $request->raiseResponseVersion(3, 0); } } } } return $request; }
/** * Gets the response format for the requested resource. * * @param RequestDescription $request The request submitted by client and it's execution result. * @param UriProcessor $uriProcessor The reference to the UriProcessor. * @param IService $service Reference to the service implementation instance * * @return string the response content-type, a null value means the requested resource * is named stream and IDSSP2::getStreamContentType returned null * * @throws ODataException, HttpHeaderFailure */ public static function getResponseContentType(RequestDescription $request, UriProcessor $uriProcessor, IService $service) { // The Accept request-header field specifies media types which are acceptable for the response $host = $service->getHost(); $requestAcceptText = $host->getRequestAccept(); $requestVersion = $request->getResponseVersion(); //if the $format header is present it overrides the accepts header $format = $host->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_FORMAT); if (!is_null($format)) { //There's a strange edge case..if application/json is supplied and it's V3 if ($format == MimeTypes::MIME_APPLICATION_JSON && $requestVersion == Version::v3()) { //then it's actual minimalmetadata //TODO: should this be done with the header text too? $format = MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META; } $requestAcceptText = ServiceHost::translateFormatToMime($requestVersion, $format); } //The response format can be dictated by the target resource kind. IE a $value will be different then expected //getTargetKind doesn't deal with link resources directly and this can change things $targetKind = $request->isLinkUri() ? TargetKind::LINK() : $request->getTargetKind(); switch ($targetKind) { case TargetKind::METADATA(): return HttpProcessUtility::selectMimeType($requestAcceptText, array(MimeTypes::MIME_APPLICATION_XML)); case TargetKind::SERVICE_DIRECTORY(): return HttpProcessUtility::selectMimeType($requestAcceptText, array(MimeTypes::MIME_APPLICATION_XML, MimeTypes::MIME_APPLICATION_ATOMSERVICE, MimeTypes::MIME_APPLICATION_JSON, MimeTypes::MIME_APPLICATION_JSON_FULL_META, MimeTypes::MIME_APPLICATION_JSON_NO_META, MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META, MimeTypes::MIME_APPLICATION_JSON_VERBOSE)); case TargetKind::PRIMITIVE_VALUE(): $supportedResponseMimeTypes = array(MimeTypes::MIME_TEXTPLAIN); if ($request->getIdentifier() != '$count') { $projectedProperty = $request->getProjectedProperty(); self::assert(!is_null($projectedProperty), '!is_null($projectedProperty)'); $type = $projectedProperty->getInstanceType(); self::assert(!is_null($type) && $type instanceof IType, '!is_null($type) && $type instanceof IType'); if ($type instanceof Binary) { $supportedResponseMimeTypes = array(MimeTypes::MIME_APPLICATION_OCTETSTREAM); } } return HttpProcessUtility::selectMimeType($requestAcceptText, $supportedResponseMimeTypes); case TargetKind::PRIMITIVE(): case TargetKind::COMPLEX_OBJECT(): case TargetKind::BAG(): case TargetKind::LINK(): return HttpProcessUtility::selectMimeType($requestAcceptText, array(MimeTypes::MIME_APPLICATION_XML, MimeTypes::MIME_TEXTXML, MimeTypes::MIME_APPLICATION_JSON, MimeTypes::MIME_APPLICATION_JSON_FULL_META, MimeTypes::MIME_APPLICATION_JSON_NO_META, MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META, MimeTypes::MIME_APPLICATION_JSON_VERBOSE)); case TargetKind::RESOURCE(): return HttpProcessUtility::selectMimeType($requestAcceptText, array(MimeTypes::MIME_APPLICATION_ATOM, MimeTypes::MIME_APPLICATION_JSON, MimeTypes::MIME_APPLICATION_JSON_FULL_META, MimeTypes::MIME_APPLICATION_JSON_NO_META, MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META, MimeTypes::MIME_APPLICATION_JSON_VERBOSE)); case TargetKind::MEDIA_RESOURCE(): if (!$request->isNamedStream() && !$request->getTargetResourceType()->isMediaLinkEntry()) { throw ODataException::createBadRequestError(Messages::badRequestInvalidUriForMediaResource($host->getAbsoluteRequestUri()->getUrlAsString())); } $uriProcessor->execute(); $request->setExecuted(); // DSSW::getStreamContentType can throw error in 2 cases // 1. If the required stream implementation not found // 2. If IDSSP::getStreamContentType returns NULL for MLE $responseContentType = $service->getStreamProviderWrapper()->getStreamContentType($request->getTargetResult(), $request->getResourceStreamInfo()); // Note StreamWrapper::getStreamContentType can return NULL if the requested named stream has not // yet been uploaded. But for an MLE if IDSSP::getStreamContentType returns NULL then StreamWrapper will throw error if (!is_null($responseContentType)) { $responseContentType = HttpProcessUtility::selectMimeType($requestAcceptText, array($responseContentType)); } return $responseContentType; } //If we got here, we just don't know what it is... throw new ODataException(Messages::unsupportedMediaType(), 415); }
public function testCreateSegments_CountSegment() { $segments = array("Customers(CustomerID='ALFKI', CustomerGuid=guid'15b242e7-52eb-46bd-8f0e-6568b72cd9a6')", 'Orders', '$count'); $segmentDescriptors = SegmentParser::parseRequestUriSegments($segments, $this->providersWrapper); $this->assertEquals(count($segmentDescriptors), 3); /** check first segment */ $this->assertEquals($segmentDescriptors[0]->getIdentifier(), 'Customers'); $this->assertEquals($segmentDescriptors[0]->getTargetKind(), TargetKind::RESOURCE()); $this->assertEquals($segmentDescriptors[0]->getTargetSource(), TargetSource::ENTITY_SET); $this->assertNull($segmentDescriptors[0]->getProjectedProperty()); $this->assertTrue($segmentDescriptors[0]->isSingleResult()); $this->assertNotNull($segmentDescriptors[0]->getTargetResourceSetWrapper()); /** check second segment */ $this->assertEquals($segmentDescriptors[1]->getIdentifier(), 'Orders'); $this->assertEquals($segmentDescriptors[1]->getTargetKind(), TargetKind::RESOURCE()); $this->assertEquals($segmentDescriptors[1]->getTargetSource(), TargetSource::PROPERTY); $this->assertNotNull($segmentDescriptors[1]->getProjectedProperty()); $this->assertFalse($segmentDescriptors[1]->isSingleResult()); $this->assertNotNull($segmentDescriptors[1]->getTargetResourceSetWrapper()); /** check third segment */ $this->assertEquals($segmentDescriptors[2]->getIdentifier(), '$count'); $this->assertEquals($segmentDescriptors[2]->getTargetKind(), TargetKind::PRIMITIVE_VALUE()); $this->assertEquals($segmentDescriptors[2]->getTargetSource(), TargetSource::PROPERTY); $this->assertNull($segmentDescriptors[2]->getProjectedProperty()); $this->assertTrue($segmentDescriptors[2]->isSingleResult()); $this->assertNotNull($segmentDescriptors[2]->getTargetResourceSetWrapper()); $this->assertNull($segmentDescriptors[2]->getKeyDescriptor()); //$count cannot be applied for singleton resource $segments = array("Customers(CustomerID='ALFKI', CustomerGuid=guid'15b242e7-52eb-46bd-8f0e-6568b72cd9a6')", 'Orders(123)', '$count'); try { SegmentParser::parseRequestUriSegments($segments, $this->providersWrapper); $this->fail('An expected ODataException singleton followed by $count has not been thrown'); } catch (ODataException $exception) { $this->assertStringEndsWith('since the segment \'Orders\' refers to a singleton, and the segment \'$count\' can only follow a resource collection.', $exception->getMessage()); } //$count cannot be applied to primitive only $vlaue is allowed for primitive $segments = array("Customers(CustomerID='ALFKI', CustomerGuid=guid'15b242e7-52eb-46bd-8f0e-6568b72cd9a6')", 'CustomerID', '$count'); try { SegmentParser::parseRequestUriSegments($segments, $this->providersWrapper); $this->fail('An expected ODataException primitive followed by non $value segment has not been thrown'); } catch (ODataException $exception) { $this->assertStringEndsWith('Since the segment \'CustomerID\' refers to a primitive type property, the only supported value from the next segment is \'$value\'.', $exception->getMessage()); } //$count cannot be applied to non-resource $segments = array("Customers(CustomerID='ALFKI', CustomerGuid=guid'15b242e7-52eb-46bd-8f0e-6568b72cd9a6')", 'Address', '$count'); try { SegmentParser::parseRequestUriSegments($segments, $this->providersWrapper); $this->fail('An expected ODataException non resource followed by $count segment has not been thrown'); } catch (ODataException $exception) { $this->assertStringEndsWith('$count cannot be applied to the segment \'Address\' since $count can only follow a resource segment.', $exception->getMessage()); } //No segments allowed after $count segment $segments = array("Customers(CustomerID='ALFKI', CustomerGuid=guid'15b242e7-52eb-46bd-8f0e-6568b72cd9a6')", 'Orders', '$count', 'OrderID'); try { SegmentParser::parseRequestUriSegments($segments, $this->providersWrapper); $this->fail('An expected ODataException non resource followed by $count segment has not been thrown'); } catch (ODataException $exception) { $this->assertStringStartsWith('The request URI is not valid. The segment \'$count\' must be the last segment in the URI because it is one of the following:', $exception->getMessage()); } }
private function createNextSegment(SegmentDescriptor $previous, $segment, $checkRights) { $previousKind = $previous->getTargetKind(); if ($previousKind == TargetKind::METADATA() || $previousKind == TargetKind::BATCH() || $previousKind == TargetKind::PRIMITIVE_VALUE() || $previousKind == TargetKind::BAG() || $previousKind == TargetKind::MEDIA_RESOURCE()) { //All these targets are terminal segments, there cannot be anything after them. throw ODataException::resourceNotFoundError(Messages::segmentParserMustBeLeafSegment($previous->getIdentifier())); } $identifier = $keyPredicate = null; $this->extractSegmentIdentifierAndKeyPredicate($segment, $identifier, $keyPredicate); $hasPredicate = !is_null($keyPredicate); $current = null; if ($previousKind == TargetKind::PRIMITIVE()) { if ($identifier !== ODataConstants::URI_VALUE_SEGMENT) { throw ODataException::resourceNotFoundError(Messages::segmentParserOnlyValueSegmentAllowedAfterPrimitivePropertySegment($identifier, $previous->getIdentifier())); } $this->_assertion(!$hasPredicate); $current = SegmentDescriptor::createFrom($previous); $current->setIdentifier(ODataConstants::URI_VALUE_SEGMENT); $current->setTargetKind(TargetKind::PRIMITIVE_VALUE()); $current->setSingleResult(true); } else { if (!is_null($previous->getPrevious()) && $previous->getPrevious()->getIdentifier() === ODataConstants::URI_LINK_SEGMENT && $identifier !== ODataConstants::URI_COUNT_SEGMENT) { throw ODataException::createBadRequestError(Messages::segmentParserNoSegmentAllowedAfterPostLinkSegment($identifier)); } else { if ($previousKind == TargetKind::RESOURCE() && $previous->isSingleResult() && $identifier === ODataConstants::URI_LINK_SEGMENT) { $this->_assertion(!$hasPredicate); $current = SegmentDescriptor::createFrom($previous); $current->setIdentifier(ODataConstants::URI_LINK_SEGMENT); $current->setTargetKind(TargetKind::LINK()); } else { //Do a sanity check here if ($previousKind != TargetKind::COMPLEX_OBJECT() && $previousKind != TargetKind::RESOURCE() && $previousKind != TargetKind::LINK()) { throw ODataException::createInternalServerError(Messages::segmentParserInconsistentTargetKindState()); } if (!$previous->isSingleResult() && $identifier !== ODataConstants::URI_COUNT_SEGMENT) { throw ODataException::createBadRequestError(Messages::segmentParserCannotQueryCollection($previous->getIdentifier())); } $current = new SegmentDescriptor(); $current->setIdentifier($identifier); $current->setTargetSource(TargetSource::PROPERTY); $projectedProperty = $previous->getTargetResourceType()->resolveProperty($identifier); $current->setProjectedProperty($projectedProperty); if ($identifier === ODataConstants::URI_COUNT_SEGMENT) { if ($previousKind != TargetKind::RESOURCE()) { throw ODataException::createBadRequestError(Messages::segmentParserCountCannotBeApplied($previous->getIdentifier())); } if ($previous->isSingleResult()) { throw ODataException::createBadRequestError(Messages::segmentParserCountCannotFollowSingleton($previous->getIdentifier())); } $current->setTargetKind(TargetKind::PRIMITIVE_VALUE()); $current->setSingleResult(true); $current->setTargetResourceSetWrapper($previous->getTargetResourceSetWrapper()); $current->setTargetResourceType($previous->getTargetResourceType()); } else { if ($identifier === ODataConstants::URI_VALUE_SEGMENT && $previousKind == TargetKind::RESOURCE()) { $current->setSingleResult(true); $current->setTargetResourceType($previous->getTargetResourceType()); $current->setTargetKind(TargetKind::MEDIA_RESOURCE()); } else { if (is_null($projectedProperty)) { if (!is_null($previous->getTargetResourceType()) && !is_null($previous->getTargetResourceType()->tryResolveNamedStreamByName($identifier))) { $current->setTargetKind(TargetKind::MEDIA_RESOURCE()); $current->setSingleResult(true); $current->setTargetResourceType($previous->getTargetResourceType()); } else { throw ODataException::createResourceNotFoundError($identifier); } } else { $current->setTargetResourceType($projectedProperty->getResourceType()); $current->setSingleResult($projectedProperty->getKind() != ResourcePropertyKind::RESOURCESET_REFERENCE); if ($previousKind == TargetKind::LINK() && $projectedProperty->getTypeKind() != ResourceTypeKind::ENTITY) { throw ODataException::createBadRequestError(Messages::segmentParserLinkSegmentMustBeFollowedByEntitySegment($identifier)); } switch ($projectedProperty->getKind()) { case ResourcePropertyKind::COMPLEX_TYPE: $current->setTargetKind(TargetKind::COMPLEX_OBJECT()); break; case ResourcePropertyKind::BAG | ResourcePropertyKind::PRIMITIVE: case ResourcePropertyKind::BAG | ResourcePropertyKind::COMPLEX_TYPE: $current->setTargetKind(TargetKind::BAG()); break; case ResourcePropertyKind::RESOURCE_REFERENCE: case ResourcePropertyKind::RESOURCESET_REFERENCE: $current->setTargetKind(TargetKind::RESOURCE()); $resourceSetWrapper = $this->providerWrapper->getResourceSetWrapperForNavigationProperty($previous->getTargetResourceSetWrapper(), $previous->getTargetResourceType(), $projectedProperty); if (is_null($resourceSetWrapper)) { throw ODataException::createResourceNotFoundError($projectedProperty->getName()); } $current->setTargetResourceSetWrapper($resourceSetWrapper); break; default: if (!$projectedProperty->isKindOf(ResourcePropertyKind::PRIMITIVE)) { throw ODataException::createInternalServerError(Messages::segmentParserUnExpectedPropertyKind('Primitive')); } $current->setTargetKind(TargetKind::PRIMITIVE()); break; } if ($hasPredicate) { $this->_assertion(!$current->isSingleResult()); $keyDescriptor = $this->_createKeyDescriptor($identifier . '(' . $keyPredicate . ')', $projectedProperty->getResourceType(), $keyPredicate); $current->setKeyDescriptor($keyDescriptor); if (!$keyDescriptor->isEmpty()) { $current->setSingleResult(true); } } if ($checkRights && !is_null($current->getTargetResourceSetWrapper())) { $current->getTargetResourceSetWrapper()->checkResourceSetRightsForRead($current->isSingleResult()); } } } } } } } return $current; }