BaseService::compareETag()   D
last analyzed

Complexity

Conditions 19
Paths 18

Size

Total Lines 78
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 380

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 33
c 2
b 0
f 0
dl 0
loc 78
ccs 0
cts 36
cp 0
rs 4.5166
cc 19
nc 18
nop 3
crap 380

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace POData;
4
5
use Exception;
6
use POData\Common\MimeTypes;
7
use POData\Common\Version;
8
use POData\OperationContext\HTTPRequestMethod;
9
use POData\Common\ErrorHandler;
10
use POData\Common\Messages;
11
use POData\Common\ODataException;
12
use POData\Common\ODataConstants;
13
use POData\Common\NotImplementedException;
14
use POData\Common\InvalidOperationException;
15
use POData\Common\HttpStatus;
16
use POData\Providers\Metadata\IMetadataProvider;
17
use POData\Providers\Metadata\Type\IType;
18
use POData\Providers\ProvidersWrapper;
19
use POData\Providers\Stream\StreamProviderWrapper;
20
use POData\Providers\Query\IQueryProvider;
21
use POData\Configuration\ServiceConfiguration;
22
use POData\UriProcessor\UriProcessor;
23
use POData\UriProcessor\RequestDescription;
24
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
25
use POData\OperationContext\ServiceHost;
26
use POData\Providers\Metadata\ResourceType;
27
use POData\Providers\Metadata\Type\Binary;
28
use POData\ObjectModel\ObjectModelSerializer;
29
use POData\Writers\Atom\AtomODataWriter;
30
use POData\Writers\Json\JsonLightMetadataLevel;
31
use POData\Writers\Json\JsonLightODataWriter;
32
use POData\Writers\Json\JsonODataV1Writer;
33
use POData\Writers\Json\JsonODataV2Writer;
34
use POData\Writers\ODataWriterRegistry;
35
use POData\Writers\ResponseWriter;
36
use POData\OperationContext\IOperationContext;
37
use POData\Common\Url;
38
39
40
/**
41
 * Class BaseService
42
 *
43
 * The base class for all BaseService specific classes. This class implements
44
 * the following interfaces:
45
 *  (1) IRequestHandler
46
 *      Implementing this interface requires defining the function
47
 *      'handleRequest' that will be invoked by dispatcher
48
 *  (2) IService
49
 *      Force BaseService class to implement functions for custom
50
 *      data service providers
51
 *
52
 * @package POData
53
 */
54
abstract class BaseService implements IRequestHandler, IService
55
{
56
    /**
57
     * The wrapper over IQueryProvider and IMetadataProvider implementations.
58
     *
59
     * @var ProvidersWrapper
60
     */
61
    private $providersWrapper;
62
63
    /**
64
     * The wrapper over IStreamProvider implementation
65
     *
66
     * @var StreamProviderWrapper
67
     */
68
    private $_streamProvider;
69
70
    /**
71
     * Hold reference to the ServiceHost instance created by dispatcher,
72
     * using this library can access headers and body of Http Request
73
     * dispatcher received and the Http Response Dispatcher is going to send.
74
     *
75
     * @var ServiceHost
76
     */
77
    private $_serviceHost;
78
79
80
    /**
81
     * To hold reference to ServiceConfiguration instance where the
82
     * service specific rules (page limit, resource set access rights
83
     * etc...) are defined.
84
     *
85
     * @var ServiceConfiguration
86
     */
87
    private $config;
88
89
    /**
90
     * Gets reference to ServiceConfiguration instance so that
91
     * service specific rules defined by the developer can be
92
     * accessed.
93
     *
94
     * @return ServiceConfiguration
95
     */
96 84
    public function getConfiguration()
97
    {
98 84
        return $this->config;
99
    }
100
101
102
    //TODO: shouldn't we hide this from the interface..if we need it at all.
103
    /**
104
     * Get the wrapper over developer's IQueryProvider and IMetadataProvider implementation.
105
     *
106
     * @return ProvidersWrapper
107
     */
108 84
    public function getProvidersWrapper()
109
    {
110 84
            return $this->providersWrapper;
111
    }
112
113
    /**
114
     * Gets reference to wrapper class instance over IDSSP implementation
115
     *
116
     * @return StreamProviderWrapper
117
     */
118
    public function getStreamProviderWrapper()
119
    {
120
        return $this->_streamProvider;
121
    }
122
123
    /**
124
     * Get reference to the data service host instance.
125
     *
126
     * @return ServiceHost
127
     */
128 87
    public function getHost()
129
    {
130 87
        return $this->_serviceHost;
131
    }
132
133
    /**
134
     * Sets the data service host instance.
135
     *
136
     * @param ServiceHost $serviceHost The data service host instance.
137
     *
138
     * @return void
139
     */
140 87
    public function setHost(ServiceHost $serviceHost)
141
    {
142 87
        $this->_serviceHost = $serviceHost;
143
    }
144
145
    /**
146
     * To get reference to operation context where we have direct access to
147
     * headers and body of Http Request we have received and the Http Response
148
     * We are going to send.
149
     *
150
     * @return IOperationContext
151
     */
152 84
    public function getOperationContext()
153
    {
154 84
        return $this->_serviceHost->getOperationContext();
155
    }
156
157
    /**
158
     * Get reference to the wrapper over IStreamProvider or
159
     * IStreamProvider2 implementations.
160
     *
161
     * @return StreamProviderWrapper
162
     */
163
    public function getStreamProvider()
164
    {
165
        if (is_null($this->_streamProvider)) {
166
            $this->_streamProvider = new StreamProviderWrapper();
167
            $this->_streamProvider->setService($this);
168
        }
169
170
        return $this->_streamProvider;
171
    }
172
173
    /**
174
     * Top-level handler invoked by Dispatcher against any request to this
175
     * service. This method will hand over request processing task to other
176
     * functions which process the request, set required headers and Response
177
     * stream (if any in Atom/Json format) in
178
     * WebOperationContext::Current()::OutgoingWebResponseContext.
179
     * Once this function returns, dispatcher uses global WebOperationContext
180
     * to write out the request response to client.
181
     * This function will perform the following operations:
182
     * (1) Check whether the top level service class implements
183
     *     IServiceProvider which means the service is a custom service, in
184
     *     this case make sure the top level service class implements
185
     *     IMetaDataProvider and IQueryProvider.
186
     *     These are the minimal interfaces that a custom service to be
187
     *     implemented in order to expose its data as OData. Save reference to
188
     *     These interface implementations.
189
     *     NOTE: Here we will ensure only providers for IDSQP and IDSMP. The
190
     *     IDSSP will be ensured only when there is an GET request on MLE/Named
191
     *     stream.
192
     *
193
     * (2). Invoke 'Initialize' method of top level service for
194
     *      collecting the configuration rules set by the developer for this
195
     *      service.
196
     *
197
     * (3). Invoke the Uri processor to process the request URI. The uri
198
     *      processor will do the following:
199
     *      (a). Validate the request uri syntax using OData uri rules
200
     *      (b). Validate the request using metadata of this service
201
     *      (c). Parse the request uri and using, IQueryProvider
202
     *           implementation, fetches the resources pointed by the uri
203
     *           if required
204
     *      (d). Build a RequestDescription which encapsulate everything
205
     *           related to request uri (e.g. type of resource, result
206
     *           etc...)
207
     * (3). Invoke handleRequest2 for further processing
208
     *
209
     * @return void
210
     */
211
    public function handleRequest()
212
    {
213
        try {
214
            $this->createProviders();
215
            $this->_serviceHost->getFullAbsoluteRequestUri()->validateQueryParameters();
216
            //$requestMethod = $this->getOperationContext()->incomingRequest()->getMethod();
217
            //if ($requestMethod != HTTPRequestMethod::GET) {
218
                # Now supporting GET and trying to support PUT
219
                //throw ODataException::createNotImplementedError(Messages::onlyReadSupport($requestMethod));
220
            //}
221
222
            $uriProcessor = UriProcessor::process($this);
223
            $request = $uriProcessor->getRequest();
224
            ob_clean();
225
            $this->serializeResult($request, $uriProcessor);
226
        } catch (Exception $exception) {
227
            ErrorHandler::handleException($exception, $this);
228
            // Return to dispatcher for writing serialized exception
229
            return;
230
        }
231
    }
232
233
    /**
234
     * @return IQueryProvider
235
     */
236
    public abstract function getQueryProvider();
237
238
    /**
239
     * @return IMetadataProvider
240
     */
241
    public abstract function getMetadataProvider();
242
243
    /**
244
     * @return \POData\Providers\Stream\IStreamProvider
245
     */
246
    public abstract function getStreamProviderX();
247
248
249
    /** @var  ODataWriterRegistry */
250
    private $writerRegistry;
251
252
    /**
253
     * Returns the ODataWriterRegistry to use when writing the response to a service document or resource request
254
     * @return ODataWriterRegistry
255
     */
256 84
    public function getODataWriterRegistry()
257
    {
258 84
        return $this->writerRegistry;
259
    }
260
261
262
    /**
263
     * This method will query and validates for IMetadataProvider and IQueryProvider implementations, invokes
264
     * BaseService::Initialize to initialize service specific policies.
265
     *
266
     *
267
     * @throws ODataException
268
     */
269 84
    protected function createProviders()
270
    {
271
272 84
        $metadataProvider = $this->getMetadataProvider();
273 84
        if (is_null($metadataProvider)) {
274
            throw ODataException::createInternalServerError(Messages::providersWrapperNull());
275
        }
276
277 84
        if (!is_object($metadataProvider) || !$metadataProvider instanceof IMetadataProvider) {
278
            throw ODataException::createInternalServerError(Messages::invalidMetadataInstance());
279
        }
280
281 84
        $queryProvider = $this->getQueryProvider();
282
283 84
        if (is_null($queryProvider)) {
284
            throw ODataException::createInternalServerError(Messages::providersWrapperNull());
285
        }
286
287 84
        if (!is_object($queryProvider)) {
288
            throw ODataException::createInternalServerError(Messages::invalidQueryInstance());
289
        }
290
291 84
        if (!$queryProvider instanceof IQueryProvider) {
0 ignored issues
show
introduced by
$queryProvider is always a sub-type of POData\Providers\Query\IQueryProvider.
Loading history...
292
            throw ODataException::createInternalServerError(Messages::invalidQueryInstance());
293
        }
294
295 84
        $this->config = new ServiceConfiguration($metadataProvider);
296 84
        $this->providersWrapper = new ProvidersWrapper(
297 84
            $metadataProvider,
298 84
            $queryProvider,
299 84
            $this->config
300 84
        );
301
302
303 84
        $this->initialize($this->config);
304
305
        //TODO: this seems like a bad spot to do this
306 84
        $this->writerRegistry = new ODataWriterRegistry();
307 84
        $this->registerWriters();
308
    }
309
310
    //TODO: i don't want this to be public..but it's the only way to test it right now...
311 87
    public function registerWriters()
312
    {
313 87
        $registry = $this->getODataWriterRegistry();
314 87
        $serviceVersion = $this->getConfiguration()->getMaxDataServiceVersion();
315 87
        $serviceURI = $this->getHost()->getAbsoluteServiceUri()->getUrlAsString();
316
317
        //We always register the v1 stuff
318 87
        $registry->register(new JsonODataV1Writer());
319 87
        $registry->register(new AtomODataWriter($serviceURI));
320
321 87
        if ($serviceVersion->compare(Version::v2()) > -1) {
322 84
            $registry->register(new JsonODataV2Writer());
323
        }
324
325 87
        if ($serviceVersion->compare(Version::v3()) > -1) {
326 44
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::NONE, $serviceURI));
327 44
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::MINIMAL, $serviceURI));
328 44
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::FULL, $serviceURI));
329
        }
330
    }
331
332
    /**
333
     * Serialize the requested resource.
334
     *
335
     * @param RequestDescription $request The description of the request  submitted by the client.
336
     * @param UriProcessor $uriProcessor Reference to the uri processor.
337
     *
338
     * @return void
339
     */
340
    protected function serializeResult(RequestDescription $request, UriProcessor $uriProcessor) {
341
        $isETagHeaderAllowed = $request->isETagHeaderAllowed();
342
        if ($this->config->getValidateETagHeader() && !$isETagHeaderAllowed) {
343
            if (!is_null($this->_serviceHost->getRequestIfMatch())
344
                ||!is_null($this->_serviceHost->getRequestIfNoneMatch())
345
            ) {
346
                throw ODataException::createBadRequestError(
347
                    Messages::eTagCannotBeSpecified($this->getHost()->getAbsoluteRequestUri()->getUrlAsString())
348
                );
349
            }
350
        }
351
352
        $responseContentType = self::getResponseContentType($request, $uriProcessor, $this);
353
354
        if (is_null($responseContentType) && $request->getTargetKind() != TargetKind::MEDIA_RESOURCE) {
0 ignored issues
show
introduced by
The condition is_null($responseContentType) is always false.
Loading history...
355
            //the responseContentType can ONLY be null if it's a stream (media resource) and that stream is storing null as the content type
356
            throw new ODataException(Messages::unsupportedMediaType(), 415);
357
        }
358
359
        $odataModelInstance = null;
360
        $hasResponseBody = true;
361
362
        $write = function($service, $request, $odataModelInstance) use ($hasResponseBody, $responseContentType) {
363
            //Note: Response content type can be null for named stream
364
            if ($hasResponseBody && !is_null($responseContentType)) {
0 ignored issues
show
introduced by
The condition is_null($responseContentType) is always false.
Loading history...
365
                if ($request->getTargetKind() != TargetKind::MEDIA_RESOURCE && $responseContentType != MimeTypes::MIME_APPLICATION_OCTETSTREAM) {
366
                    //append charset for everything except:
367
                    //stream resources as they have their own content type
368
                    //binary properties (they content type will be App Octet for those...is this a good way? we could also decide based upon the projected property
369
                    //
370
                    $responseContentType .= ';charset=utf-8';
371
                }
372
            }
373
374
            if ($hasResponseBody) {
0 ignored issues
show
introduced by
The condition $hasResponseBody is always true.
Loading history...
375
                ResponseWriter::write(
376
                    $service,
377
                    $request,
378
                    $odataModelInstance,
379
                    $responseContentType
380
                );
381
            }
382
        };
383
384
        // Execution required at this point if request target to any resource other than
385
        //
386
        // (1) media resource - For Media resource 'getResponseContentType' already performed execution as it needs to know the mime type of the stream
387
        // (2) metadata - internal resource
388
        // (3) service directory - internal resource
389
        if (!in_array($request->getTargetKind(), [TargetKind::MEDIA_RESOURCE, TargetKind::METADATA, TargetKind::SERVICE_DIRECTORY])) {
390
391
            if ($request->needExecution()) {
392
                $uriProcessor->execute();
393
            }
394
395
            $objectModelSerializer = new ObjectModelSerializer($this, $request);
396
            if (!$request->isSingleResult()) {
397
                // Code path for collection (feed or links)
398
                $entryObjects = $request->getTargetResult();
399
                self::assert(
400
                    !is_null($entryObjects) && is_iterable($entryObjects),
401
                    '!is_null($entryObjects) && is_iterable($entryObjects)'
402
                );
403
                // If related resource set is empty for an entry then we should
404
                // not throw error instead response must be empty feed or empty links
405
                if ($request->isLinkUri()) {
406
                    $odataModelInstance = $objectModelSerializer->writeUrlElements($entryObjects);
407
                    self::assert(
408
                        $odataModelInstance instanceof \POData\ObjectModel\ODataURLCollection,
409
                        '$odataModelInstance instanceof ODataURLCollection'
410
                    );
411
                } else {
412
                    if (!empty($request->getParts())) {
413
                        $context = $this->_serviceHost->getOperationContext();
414
                        $response = $context->outgoingResponse();
415
                        $boundary = uniqid('batch_');
416
                        $streams = [];
417
                        foreach ($request->getParts() as $key => $part) {
418
                            $_objectModelSerializer = new ObjectModelSerializer($this, $part);
0 ignored issues
show
Unused Code introduced by
The assignment to $_objectModelSerializer is dead and can be removed.
Loading history...
419
                            $status = "200 Ok";
420
                            try {
421
                                $this->serializeResult($part, $uriProcessor);
422
                            } catch (Exception $exception) {
423
                                ErrorHandler::handleException($exception, $this);
424
                                $status = "500 Internal Server Error";
425
                            }
426
                            $body = $response->getStream();
427
                            $stream = [
428
                                'body' => $body,
429
                                'length' => strlen($body),
430
                                'content_id' => $part->getContentID(),
431
                                'status' => $status,
432
                                'Content-Type' => $response->getHeaders()['Content-Type']
433
                            ];
434
                            $streams[] = $stream;
435
                        }
436
437
                        $output_stream = '';
438
                        foreach ($streams as $stream) {
439
                            $output_stream .= "--{$boundary}\r\n";
440
                            if (!is_null($stream['content_id'])) {
441
                                $output_stream .= "Content-ID: {$stream['content_id']}\r\n";
442
                            }
443
                            $output_stream .= "Content-Type: application/http\r\n";
444
                            $output_stream .= "Content-Transfer-Encoding: binary\r\n";
445
                            $output_stream .= "\r\n";
446
                            $output_stream .= "{$_SERVER['SERVER_PROTOCOL']} {$stream['status']}\r\n";
447
                            $output_stream .= "Content-Type: {$stream['Content-Type']}\r\n";
448
                            $output_stream .= "Content-Length: {$stream['length']}\r\n";
449
                            $output_stream .= "\r\n";
450
                            $output_stream .= $stream['body'] . "\r\n";
451
                            $output_stream .= "\r\n";
452
                        }
453
                        $output_stream .= "--{$boundary}--\r\n";
454
                        $response->setStream($output_stream);
455
                        $this->_serviceHost->setResponseStatusCode(HttpStatus::CODE_ACCEPTED);
456
                        $this->_serviceHost->setResponseContentType("multipart/mixed; boundary={$boundary}");
457
                        return;
458
                    } else {
459
                        $odataModelInstance = $objectModelSerializer->writeTopLevelElements($entryObjects);
460
                        self::assert(
461
                            $odataModelInstance instanceof \POData\ObjectModel\ODataFeed,
462
                            '$odataModelInstance instanceof ODataFeed'
463
                        );
464
                    }
465
                }
466
            } else {
467
                // Code path for entry, complex, bag, resource reference link,
468
                // primitive type or primitive value
469
                $result = $request->getTargetResult();
470
                $requestTargetKind = $request->getTargetKind();
471
                if ($request->isLinkUri()) {
472
                    // In the query 'Orders(1245)/$links/Customer', the targeted
473
                    // Customer might be null
474
                    if (is_null($result)) {
475
                        throw ODataException::createResourceNotFoundError(
476
                            $request->getIdentifier()
477
                        );
478
                    }
479
480
                    $odataModelInstance = $objectModelSerializer->writeUrlElement($result);
481
                } else if ($requestTargetKind == TargetKind::RESOURCE) {
482
                    if (!is_null($this->_serviceHost->getRequestIfMatch())
483
                        && !is_null($this->_serviceHost->getRequestIfNoneMatch())
484
                    ) {
485
                        throw ODataException::createBadRequestError(
486
                            Messages::bothIfMatchAndIfNoneMatchHeaderSpecified()
487
                        );
488
                    }
489
                    // handle entry resource
490
                    $needToSerializeResponse = true;
491
                    $targetResourceType = $request->getTargetResourceType();
492
                    $eTag = $this->compareETag(
493
                        $result,
494
                        $targetResourceType,
495
                        $needToSerializeResponse
496
                    );
497
498
                    if ($needToSerializeResponse) {
499
                        if (is_null($result)) {
500
                            // In the query 'Orders(1245)/Customer', the targeted
501
                            // Customer might be null
502
                            // set status code to 204 => 'No Content'
503
                            $this->_serviceHost->setResponseStatusCode(HttpStatus::CODE_NOCONTENT);
504
                            $hasResponseBody = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $hasResponseBody is dead and can be removed.
Loading history...
505
                        } else {
506
                            $odataModelInstance = $objectModelSerializer->writeTopLevelElement($result);
507
                        }
508
                    } else {
509
                        // Resource is not modified so set status code
510
                        // to 304 => 'Not Modified'
511
                        $this->_serviceHost->setResponseStatusCode(HttpStatus::CODE_NOT_MODIFIED);
512
                        $hasResponseBody = false;
513
                    }
514
515
                    // if resource has eTagProperty then eTag header needs to written
516
                    if (!is_null($eTag)) {
517
                        $this->_serviceHost->setResponseETag($eTag);
518
                    }
519
                } else if ($requestTargetKind == TargetKind::COMPLEX_OBJECT) {
520
                    $targetResourceType = $request->getTargetResourceType();
521
                    $odataModelInstance = $objectModelSerializer->writeTopLevelComplexObject(
522
                        $result,
523
                        $request->getProjectedProperty()->getName(),
524
                        $targetResourceType
525
                    );
526
                } else if ($requestTargetKind == TargetKind::BAG) {
527
                    $targetResourceType = $request->getTargetResourceType();
528
                    $odataModelInstance = $objectModelSerializer->writeTopLevelBagObject(
529
                        $result,
530
                        $request->getProjectedProperty()->getName(),
531
                        $targetResourceType
532
                    );
533
                } else if ($requestTargetKind == TargetKind::PRIMITIVE) {
534
                    $projectedProperty = $request->getProjectedProperty();
535
                    $odataModelInstance = $objectModelSerializer->writeTopLevelPrimitive(
536
                        $result,
537
                        $projectedProperty
538
                    );
539
                } else if ($requestTargetKind == TargetKind::PRIMITIVE_VALUE) {
540
                    // Code path for primitive value (Since its primitve no need for
541
                    // object model serialization)
542
                    // Customers('ANU')/CompanyName/$value => string
543
                    // Employees(1)/Photo/$value => binary stream
544
                    // Customers/$count => string
545
                } else {
546
                    self::assert(false, 'Unexpected resource target kind');
547
                }
548
            }
549
        }
550
551
        $write($this, $request, $odataModelInstance);
552
    }
553
554
    /**
555
     * Gets the response format for the requested resource.
556
     *
557
     * @param RequestDescription $request The request submitted by client and it's execution result.
558
     * @param UriProcessor $uriProcessor The reference to the UriProcessor.
559
     * @param IService $service Reference to the service implementation instance
560
     *
561
     * @return string the response content-type, a null value means the requested resource
562
     * is named stream and IDSSP2::getStreamContentType returned null
563
     *
564
     * @throws ODataException, HttpHeaderFailure
565
     */
566 146
    public static function getResponseContentType(
567
        RequestDescription $request,
568
        UriProcessor $uriProcessor,
569
        IService $service
570
    ) {
571
572
        // The Accept request-header field specifies media types which are acceptable for the response
573
574 146
        $host = $service->getHost();
575 146
        $requestAcceptText = $host->getRequestAccept();
576 146
        $requestVersion = $request->getResponseVersion();
577
578
        //if the $format header is present it overrides the accepts header
579 146
        $format = $request->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_FORMAT);
580 146
        if (!is_null($format)) {
581
582
            //There's a strange edge case..if application/json is supplied and it's V3
583 36
            if ($format == MimeTypes::MIME_APPLICATION_JSON && $requestVersion == Version::v3()) {
584
                //then it's actual minimalmetadata
585
                //TODO: should this be done with the header text too?
586 1
                $format = MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META;
587
            }
588
589 36
            $requestAcceptText = ServiceHost::translateFormatToMime($requestVersion, $format);
590
        }
591
592
593
594
595
        //The response format can be dictated by the target resource kind. IE a $value will be different then expected
596
        //getTargetKind doesn't deal with link resources directly and this can change things
597 146
        $targetKind = $request->isLinkUri() ? TargetKind::LINK : $request->getTargetKind();
598
599
        switch ($targetKind) {
600
            case TargetKind::METADATA:
601 18
                return HttpProcessUtility::selectMimeType(
602 18
                    $requestAcceptText,
603 18
                    array(MimeTypes::MIME_APPLICATION_XML)
604 18
                );
605
606
            case TargetKind::SERVICE_DIRECTORY:
607 19
                return HttpProcessUtility::selectMimeType(
608 19
                    $requestAcceptText,
609 19
                    array(
610 19
                        MimeTypes::MIME_APPLICATION_XML,
611 19
                        MimeTypes::MIME_APPLICATION_ATOMSERVICE,
612 19
                        MimeTypes::MIME_APPLICATION_JSON,
613 19
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
614 19
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
615 19
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
616 19
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
617
618 19
                    )
619 19
                );
620
621
            case TargetKind::PRIMITIVE_VALUE:
622
                $supportedResponseMimeTypes = array(MimeTypes::MIME_TEXTPLAIN);
623
624
                if ($request->getIdentifier() != '$count') {
625
                    $projectedProperty = $request->getProjectedProperty();
626
                    self::assert(
627
                        !is_null($projectedProperty),
628
                        '!is_null($projectedProperty)'
629
                    );
630
                    $type = $projectedProperty->getInstanceType();
631
                    self::assert(
632
                        !is_null($type) && $type instanceof IType,
633
                        '!is_null($type) && $type instanceof IType'
634
                    );
635
                    if ($type instanceof Binary) {
636
                        $supportedResponseMimeTypes = array(MimeTypes::MIME_APPLICATION_OCTETSTREAM);
637
                    }
638
                }
639
640
                return HttpProcessUtility::selectMimeType(
641
                    $requestAcceptText,
642
                    $supportedResponseMimeTypes
643
                );
644
645
            case TargetKind::PRIMITIVE:
646
            case TargetKind::COMPLEX_OBJECT:
647
            case TargetKind::BAG:
648
            case TargetKind::LINK:
649 88
                return HttpProcessUtility::selectMimeType(
650 88
                    $requestAcceptText,
651 88
                    array(
652 88
                        MimeTypes::MIME_APPLICATION_XML,
653 88
                        MimeTypes::MIME_TEXTXML,
654 88
                        MimeTypes::MIME_APPLICATION_JSON,
655 88
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
656 88
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
657 88
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
658 88
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
659 88
                    )
660 88
                );
661
662
            case TargetKind::BATCH:
663
            case TargetKind::RESOURCE:
664 21
                return HttpProcessUtility::selectMimeType(
665 21
                    $requestAcceptText,
666 21
                    array(
667 21
                        MimeTypes::MIME_APPLICATION_ATOM,
668 21
                        MimeTypes::MIME_APPLICATION_JSON,
669 21
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
670 21
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
671 21
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
672 21
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
673 21
                    )
674 21
                );
675
676
            case TargetKind::MEDIA_RESOURCE:
677
                if (!$request->isNamedStream() && !$request->getTargetResourceType()->isMediaLinkEntry()) {
678
                    throw ODataException::createBadRequestError(
679
                        Messages::badRequestInvalidUriForMediaResource(
680
                            $host->getAbsoluteRequestUri()->getUrlAsString()
681
                        )
682
                    );
683
                }
684
685
                $uriProcessor->execute();
686
                $request->setExecuted();
687
                // DSSW::getStreamContentType can throw error in 2 cases
688
                // 1. If the required stream implementation not found
689
                // 2. If IDSSP::getStreamContentType returns NULL for MLE
690
                $responseContentType = $service->getStreamProviderWrapper()
691
                    ->getStreamContentType(
692
                        $request->getTargetResult(),
0 ignored issues
show
Bug introduced by
It seems like $request->getTargetResult() can also be of type array and array; however, parameter $entity of POData\Providers\Stream\...:getStreamContentType() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

692
                        /** @scrutinizer ignore-type */ $request->getTargetResult(),
Loading history...
693
                        $request->getResourceStreamInfo()
694
                    );
695
696
697
                // Note StreamWrapper::getStreamContentType can return NULL if the requested named stream has not
698
                // yet been uploaded. But for an MLE if IDSSP::getStreamContentType returns NULL then StreamWrapper will throw error
699
                if (!is_null($responseContentType)) {
0 ignored issues
show
introduced by
The condition is_null($responseContentType) is always false.
Loading history...
700
                    $responseContentType = HttpProcessUtility::selectMimeType(
701
                        $requestAcceptText,
702
                        array($responseContentType)
703
                    );
704
                }
705
706
                return $responseContentType;
707
        }
708
709
710
        //If we got here, we just don't know what it is...
711
        throw new ODataException(Messages::unsupportedMediaType(), 415);
712
713
    }
714
715
716
717
    /**
718
     * For the given entry object compare it's eTag (if it has eTag properties)
719
     * with current eTag request headers (if it present).
720
     *
721
     * @param mixed        &$entryObject             entity resource for which etag
722
     *                                               needs to be checked.
723
     * @param ResourceType &$resourceType            Resource type of the entry
724
     *                                               object.
725
     * @param boolean      &$needToSerializeResponse On return, this will contain
726
     *                                               True if response needs to be
727
     *                                               serialized, False otherwise.
728
     * @param boolean $needToSerializeResponse
729
     *
730
     * @return string|null The ETag for the entry object if it has eTag properties
731
     *                     NULL otherwise.
732
     */
733
    protected function compareETag(&$entryObject, ResourceType &$resourceType,
734
        &$needToSerializeResponse
735
    ) {
736
        $needToSerializeResponse = true;
737
        $eTag = null;
738
        $ifMatch = $this->_serviceHost->getRequestIfMatch();
739
        $ifNoneMatch = $this->_serviceHost->getRequestIfNoneMatch();
740
        if (is_null($entryObject)) {
741
            if (!is_null($ifMatch)) {
742
                throw ODataException::createPreConditionFailedError(
743
                    Messages::eTagNotAllowedForNonExistingResource()
744
                );
745
            }
746
747
            return null;
748
        }
749
750
        if ($this->config->getValidateETagHeader() && !$resourceType->hasETagProperties()) {
751
            if (!is_null($ifMatch) || !is_null($ifNoneMatch)) {
752
                // No eTag properties but request has eTag headers, bad request
753
                throw ODataException::createBadRequestError(
754
                    Messages::noETagPropertiesForType()
755
                );
756
            }
757
758
            // We need write the response but no eTag header
759
            return null;
760
        }
761
762
        if (!$this->config->getValidateETagHeader()) {
763
            // Configuration says do not validate ETag so we will not write ETag header in the
764
            // response even though the requested resource support it
765
            return null;
766
        }
767
768
        if (is_null($ifMatch) && is_null($ifNoneMatch)) {
769
            // No request eTag header, we need to write the response
770
            // and eTag header
771
        } else if (is_string($ifMatch) && strcmp($ifMatch, '*') == 0) {
772
            // If-Match:* => we need to write the response and eTag header
773
        } else if (is_string($ifNoneMatch) && strcmp($ifNoneMatch, '*') == 0) {
774
            // if-None-Match:* => Do not write the response (304 not modified),
775
            // but write eTag header
776
            $needToSerializeResponse = false;
777
        } else {
778
            $eTag = $this->getETagForEntry($entryObject, $resourceType);
779
            // Note: The following code for attaching the prefix W\"
780
            // and the suffix " can be done in getETagForEntry function
781
            // but that is causing an issue in Linux env where the
782
            // firefix browser is unable to parse the ETag in this case.
783
            // Need to follow up PHP core devs for this.
784
            $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
785
            if (!is_null($ifMatch)) {
786
                if (strcmp($eTag, $ifMatch) != 0) {
787
                    // Requested If-Match value does not match with current
788
                    // eTag Value then pre-condition error
789
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
790
                    throw ODataException::createPreConditionFailedError(
791
                        Messages::eTagValueDoesNotMatch()
792
                    );
793
                }
794
            } else if (is_string($ifNoneMatch) && strcmp($eTag, $ifNoneMatch) == 0) {
795
                //304 not modified, but in write eTag header
796
                $needToSerializeResponse = false;
797
            }
798
        }
799
800
        if (is_null($eTag)) {
801
            $eTag = $this->getETagForEntry($entryObject, $resourceType);
802
            // Note: The following code for attaching the prefix W\"
803
            // and the suffix " can be done in getETagForEntry function
804
            // but that is causing an issue in Linux env where the
805
            // firefix browser is unable to parse the ETag in this case.
806
            // Need to follow up PHP core devs for this.
807
            $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
808
        }
809
810
        return $eTag;
811
    }
812
813
    /**
814
     * Returns the etag for the given resource.
815
     * Note: This function will not add W\" prefix and " suffix, its callers
816
     * repsonsability.
817
     *
818
     * @param mixed        &$entryObject  Resource for which etag value needs to
819
     *                                    be returned
820
     * @param ResourceType &$resourceType Resource type of the $entryObject
821
     *
822
     * @return string|null ETag value for the given resource (with values encoded
823
     *                     for use in a URI) there are etag properties, NULL if
824
     *                     there is no etag property.
825
     */
826
    protected function getETagForEntry(&$entryObject, ResourceType &$resourceType)
827
    {
828
        $eTag = null;
829
        $comma = null;
830
        foreach ($resourceType->getETagProperties() as $eTagProperty) {
831
            $type = $eTagProperty->getInstanceType();
832
            self::assert(
833
                !is_null($type) && $type instanceof IType,
834
                '!is_null($type) && $type instanceof IType'
835
            );
836
837
            $value = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $value is dead and can be removed.
Loading history...
838
            try {
839
840
                //TODO #88...also this seems like dupe work
841
                $reflectionProperty = new \ReflectionProperty($entryObject, $eTagProperty->getName());
842
                $value = $reflectionProperty->getValue($entryObject);
843
            } catch (\ReflectionException $reflectionException) {
844
                throw ODataException::createInternalServerError(
845
                    Messages::failedToAccessProperty($eTagProperty->getName(), $resourceType->getName())
846
                );
847
            }
848
849
            if (is_null($value)) {
850
                $eTag = $eTag . $comma . 'null';
851
            } else {
852
                $eTag = $eTag . $comma . $type->convertToOData($value);
0 ignored issues
show
Bug introduced by
The method convertToOData() does not exist on ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

852
                $eTag = $eTag . $comma . $type->/** @scrutinizer ignore-call */ convertToOData($value);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
853
            }
854
855
            $comma = ',';
856
        }
857
858
        if (!is_null($eTag)) {
859
            // If eTag is made up of datetime or string properties then the above
860
            // IType::convertToOData will perform utf8 and url encode. But we don't
861
            // want this for eTag value.
862
            $eTag = urldecode(utf8_decode($eTag));
863
            return rtrim($eTag, ',');
864
        }
865
866
        return null;
867
    }
868
869
    /**
870
     * This function will perform the following operations:
871
     * (1) Invoke delegateRequestProcessing method to process the request based
872
     *     on request method (GET, PUT/MERGE, POST, DELETE)
873
     * (3) If the result of processing of request needs to be serialized as HTTP
874
     *     response body (e.g. GET request result in single resource or resource
875
     *     collection, successful POST operation for an entity need inserted
876
     *     entity to be serialized back etc..), Serialize the result by using
877
     *     'serializeReultForResponseBody' method
878
     *     Set the serialized result to
879
     *     WebOperationContext::Current()::OutgoingWebResponseContext::Stream.
880
     *
881
     *     @return void
882
     */
883
    protected function handleRequest2()
884
    {
885
    }
886
887
    /**
888
     * This method will perform the following operations:
889
     * (1) If request method is GET, then result is already there in the
890
     *     RequestDescription so simply return the RequestDescription
891
     * (2). If request method is for CDU
892
     *      (Create/Delete/Update - POST/DELETE/PUT-MERGE) hand
893
     *      over the responsibility to respective handlers. The handler
894
     *      methods are:
895
     *      (a) handlePOSTOperation() => POST
896
     *      (b) handlePUTOperation() => PUT/MERGE
897
     *      (c) handleDELETEOperation() => DELETE
898
     * (3). Check whether its required to write any result to the response
899
     *      body
900
     *      (a). Request method is GET
901
     *      (b). Request is a POST for adding NEW Entry
902
     *      (c). Request is a POST for adding Media Resource Stream
903
     *      (d). Request is a POST for adding a link
904
     *      (e). Request is a DELETE for deleting entry or relationship
905
     *      (f). Request is a PUT/MERGE for updating an entry
906
     *      (g). Request is a PUT for updating a link
907
     *     In case a, b and c we need to write the result to response body,
908
     *     for d, e, f and g no body content.
909
     *
910
     * @return RequestDescription|null Instance of RequestDescription with
911
     *         result to be write back Null if no result to write.
912
     */
913
    protected function delegateRequestProcessing()
914
    {
915
    }
916
917
    /**
918
     * Serialize the result in the current request description using
919
     * appropriate odata writer (AtomODataWriter/JSONODataWriter)
920
     *
921
     * @return void
922
     *
923
     */
924
    protected function serializeReultForResponseBody()
925
    {
926
    }
927
928
    /**
929
     * Handle POST request.
930
     *
931
     * @return void
932
     *
933
     * @throws NotImplementedException
934
     */
935
    protected function handlePOSTOperation()
936
    {
937
    }
938
939
    /**
940
     * Handle PUT/MERGE request.
941
     *
942
     * @return void
943
     *
944
     * @throws NotImplementedException
945
     */
946
    protected function handlePUTOperation()
947
    {
948
    }
949
950
    /**
951
     * Handle DELETE request.
952
     *
953
     * @return void
954
     *
955
     * @throws NotImplementedException
956
     */
957
    protected function handleDELETEOperation()
958
    {
959
    }
960
961
    /**
962
     * Assert that the given condition is true.
963
     *
964
     * @param boolean $condition         The condtion to check.
965
     * @param string  $conditionAsString Message to show if assertion fails.
966
     *
967
     * @return void
968
     *
969
     * @throws InvalidOperationException
970
     */
971
    protected static function assert($condition, $conditionAsString)
972
    {
973
        if (!$condition) {
974
            throw new InvalidOperationException(
975
                "Unexpected state, expecting $conditionAsString"
976
            );
977
        }
978
    }
979
}
980