Passed
Push — master ( 4ab488...bcfbc7 )
by Bálint
03:58
created

BaseService::getResponseContentType()   D

Complexity

Conditions 21
Paths 90

Size

Total Lines 146
Code Lines 83

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 83
dl 0
loc 146
c 0
b 0
f 0
rs 4.1666
cc 21
nc 90
nop 3

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
38
39
/**
40
 * Class BaseService
41
 *
42
 * The base class for all BaseService specific classes. This class implements
43
 * the following interfaces:
44
 *  (1) IRequestHandler
45
 *      Implementing this interface requires defining the function
46
 *      'handleRequest' that will be invoked by dispatcher
47
 *  (2) IService
48
 *      Force BaseService class to implement functions for custom
49
 *      data service providers
50
 *
51
 * @package POData
52
 */
53
abstract class BaseService implements IRequestHandler, IService
54
{
55
    /**
56
     * The wrapper over IQueryProvider and IMetadataProvider implementations.
57
     *
58
     * @var ProvidersWrapper
59
     */
60
    private $providersWrapper;
61
62
    /**
63
     * The wrapper over IStreamProvider implementation
64
     *
65
     * @var StreamProviderWrapper
66
     */
67
    private $_streamProvider;
68
69
    /**
70
     * Hold reference to the ServiceHost instance created by dispatcher,
71
     * using this library can access headers and body of Http Request
72
     * dispatcher received and the Http Response Dispatcher is going to send.
73
     *
74
     * @var ServiceHost
75
     */
76
    private $_serviceHost;
77
78
79
    /**
80
     * To hold reference to ServiceConfiguration instance where the
81
     * service specific rules (page limit, resource set access rights
82
     * etc...) are defined.
83
     *
84
     * @var ServiceConfiguration
85
     */
86
    private $config;
87
88
    /**
89
     * Gets reference to ServiceConfiguration instance so that
90
     * service specific rules defined by the developer can be
91
     * accessed.
92
     *
93
     * @return ServiceConfiguration
94
     */
95
    public function getConfiguration()
96
    {
97
        return $this->config;
98
    }
99
100
101
    //TODO: shouldn't we hide this from the interface..if we need it at all.
102
    /**
103
     * Get the wrapper over developer's IQueryProvider and IMetadataProvider implementation.
104
     *
105
     * @return ProvidersWrapper
106
     */
107
    public function getProvidersWrapper()
108
    {
109
            return $this->providersWrapper;
110
    }
111
112
    /**
113
     * Gets reference to wrapper class instance over IDSSP implementation
114
     *
115
     * @return StreamProviderWrapper
116
     */
117
    public function getStreamProviderWrapper()
118
    {
119
        return $this->_streamProvider;
120
    }
121
122
    /**
123
     * Get reference to the data service host instance.
124
     *
125
     * @return ServiceHost
126
     */
127
    public function getHost()
128
    {
129
        return $this->_serviceHost;
130
    }
131
132
    /**
133
     * Sets the data service host instance.
134
     *
135
     * @param ServiceHost $serviceHost The data service host instance.
136
     *
137
     * @return void
138
     */
139
    public function setHost(ServiceHost $serviceHost)
140
    {
141
        $this->_serviceHost = $serviceHost;
142
    }
143
144
    /**
145
     * To get reference to operation context where we have direct access to
146
     * headers and body of Http Request we have received and the Http Response
147
     * We are going to send.
148
     *
149
     * @return IOperationContext
150
     */
151
    public function getOperationContext()
152
    {
153
        return $this->_serviceHost->getOperationContext();
154
    }
155
156
    /**
157
     * Get reference to the wrapper over IStreamProvider or
158
     * IStreamProvider2 implementations.
159
     *
160
     * @return StreamProviderWrapper
161
     */
162
    public function getStreamProvider()
163
    {
164
        if (is_null($this->_streamProvider)) {
165
            $this->_streamProvider = new StreamProviderWrapper();
166
            $this->_streamProvider->setService($this);
167
        }
168
169
        return $this->_streamProvider;
170
    }
171
172
    /**
173
     * Top-level handler invoked by Dispatcher against any request to this
174
     * service. This method will hand over request processing task to other
175
     * functions which process the request, set required headers and Response
176
     * stream (if any in Atom/Json format) in
177
     * WebOperationContext::Current()::OutgoingWebResponseContext.
178
     * Once this function returns, dispatcher uses global WebOperationContext
179
     * to write out the request response to client.
180
     * This function will perform the following operations:
181
     * (1) Check whether the top level service class implements
182
     *     IServiceProvider which means the service is a custom service, in
183
     *     this case make sure the top level service class implements
184
     *     IMetaDataProvider and IQueryProvider.
185
     *     These are the minimal interfaces that a custom service to be
186
     *     implemented in order to expose its data as OData. Save reference to
187
     *     These interface implementations.
188
     *     NOTE: Here we will ensure only providers for IDSQP and IDSMP. The
189
     *     IDSSP will be ensured only when there is an GET request on MLE/Named
190
     *     stream.
191
     *
192
     * (2). Invoke 'Initialize' method of top level service for
193
     *      collecting the configuration rules set by the developer for this
194
     *      service.
195
     *
196
     * (3). Invoke the Uri processor to process the request URI. The uri
197
     *      processor will do the following:
198
     *      (a). Validate the request uri syntax using OData uri rules
199
     *      (b). Validate the request using metadata of this service
200
     *      (c). Parse the request uri and using, IQueryProvider
201
     *           implementation, fetches the resources pointed by the uri
202
     *           if required
203
     *      (d). Build a RequestDescription which encapsulate everything
204
     *           related to request uri (e.g. type of resource, result
205
     *           etc...)
206
     * (3). Invoke handleRequest2 for further processing
207
     *
208
     * @return void
209
     */
210
    public function handleRequest()
211
    {
212
        try {
213
            $this->createProviders();
214
            $this->_serviceHost->validateQueryParameters();
215
            //$requestMethod = $this->getOperationContext()->incomingRequest()->getMethod();
216
            //if ($requestMethod != HTTPRequestMethod::GET()) {
217
                # Now supporting GET and trying to support PUT
218
                //throw ODataException::createNotImplementedError(Messages::onlyReadSupport($requestMethod));
219
            //}
220
221
            $uriProcessor = UriProcessor::process($this);
222
            $request = $uriProcessor->getRequest();
223
            ob_clean();
224
            $this->serializeResult($request, $uriProcessor);
225
        } catch (Exception $exception) {
226
            ErrorHandler::handleException($exception, $this);
227
            // Return to dispatcher for writing serialized exception
228
            return;
229
        }
230
    }
231
232
    /**
233
     * @return IQueryProvider
234
     */
235
    public abstract function getQueryProvider();
236
237
    /**
238
     * @return IMetadataProvider
239
     */
240
    public abstract function getMetadataProvider();
241
242
    /**
243
     * @return \POData\Providers\Stream\IStreamProvider
244
     */
245
    public abstract function getStreamProviderX();
246
247
248
    /** @var  ODataWriterRegistry */
249
    private $writerRegistry;
250
251
    /**
252
     * Returns the ODataWriterRegistry to use when writing the response to a service document or resource request
253
     * @return ODataWriterRegistry
254
     */
255
    public function getODataWriterRegistry()
256
    {
257
        return $this->writerRegistry;
258
    }
259
260
261
    /**
262
     * This method will query and validates for IMetadataProvider and IQueryProvider implementations, invokes
263
     * BaseService::Initialize to initialize service specific policies.
264
     *
265
     *
266
     * @throws ODataException
267
     */
268
    protected function createProviders()
269
    {
270
271
        $metadataProvider = $this->getMetadataProvider();
272
        if (is_null($metadataProvider)) {
273
            throw ODataException::createInternalServerError(Messages::providersWrapperNull());
274
        }
275
276
        if (!is_object($metadataProvider) || !$metadataProvider instanceof IMetadataProvider) {
277
            throw ODataException::createInternalServerError(Messages::invalidMetadataInstance());
278
        }
279
280
        $queryProvider = $this->getQueryProvider();
281
282
        if (is_null($queryProvider)) {
283
            throw ODataException::createInternalServerError(Messages::providersWrapperNull());
284
        }
285
286
        if (!is_object($queryProvider)) {
287
            throw ODataException::createInternalServerError(Messages::invalidQueryInstance());
288
        }
289
290
        if (!$queryProvider instanceof IQueryProvider) {
0 ignored issues
show
introduced by
$queryProvider is always a sub-type of POData\Providers\Query\IQueryProvider.
Loading history...
291
            throw ODataException::createInternalServerError(Messages::invalidQueryInstance());
292
        }
293
294
        $this->config = new ServiceConfiguration($metadataProvider);
295
        $this->providersWrapper = new ProvidersWrapper(
296
            $metadataProvider,
297
            $queryProvider,
298
            $this->config
299
        );
300
301
302
        $this->initialize($this->config);
303
304
        //TODO: this seems like a bad spot to do this
305
        $this->writerRegistry = new ODataWriterRegistry();
306
        $this->registerWriters();
307
    }
308
309
    //TODO: i don't want this to be public..but it's the only way to test it right now...
310
    public function registerWriters()
311
    {
312
        $registry = $this->getODataWriterRegistry();
313
        $serviceVersion = $this->getConfiguration()->getMaxDataServiceVersion();
314
        $serviceURI = $this->getHost()->getAbsoluteServiceUri()->getUrlAsString();
315
316
        //We always register the v1 stuff
317
        $registry->register(new JsonODataV1Writer());
318
        $registry->register(new AtomODataWriter($serviceURI));
319
320
        if ($serviceVersion->compare(Version::v2()) > -1) {
321
            $registry->register(new JsonODataV2Writer());
322
        }
323
324
        if ($serviceVersion->compare(Version::v3()) > -1) {
325
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::NONE(), $serviceURI));
326
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::MINIMAL(), $serviceURI));
327
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::FULL(), $serviceURI));
328
        }
329
    }
330
331
    /**
332
     * Serialize the requested resource.
333
     *
334
     * @param RequestDescription $request The description of the request  submitted by the client.
335
     * @param UriProcessor $uriProcessor Reference to the uri processor.
336
     *
337
     * @return void
338
     */
339
    protected function serializeResult(RequestDescription $request, UriProcessor $uriProcessor) {
340
        $isETagHeaderAllowed = $request->isETagHeaderAllowed();
341
        if ($this->config->getValidateETagHeader() && !$isETagHeaderAllowed) {
342
            if (!is_null($this->_serviceHost->getRequestIfMatch())
0 ignored issues
show
introduced by
The condition is_null($this->_serviceHost->getRequestIfMatch()) is always false.
Loading history...
343
                ||!is_null($this->_serviceHost->getRequestIfNoneMatch())
344
            ) {
345
                throw ODataException::createBadRequestError(
346
                    Messages::eTagCannotBeSpecified($this->getHost()->getAbsoluteRequestUri()->getUrlAsString())
347
                );
348
            }
349
        }
350
351
        $responseContentType = self::getResponseContentType($request, $uriProcessor, $this);
352
353
        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...
354
            //the responseContentType can ONLY be null if it's a stream (media resource) and that stream is storing null as the content type
355
            throw new ODataException(Messages::unsupportedMediaType(), 415);
356
        }
357
358
        $odataModelInstance = null;
359
        $hasResponseBody = true;
360
        // Execution required at this point if request target to any resource other than
361
        //
362
        // (1) media resource - For Media resource 'getResponseContentType' already performed execution as it needs to know the mime type of the stream
363
        // (2) metadata - internal resource
364
        // (3) service directory - internal resource
365
        if ($request->needExecution()) {
366
            $uriProcessor->execute();
367
            $objectModelSerializer = new ObjectModelSerializer($this, $request);
368
            if (!$request->isSingleResult()) {
369
                // Code path for collection (feed or links)
370
                $entryObjects = $request->getTargetResult();
371
                self::assert(
372
                    !is_null($entryObjects) && is_array($entryObjects),
373
                    '!is_null($entryObjects) && is_array($entryObjects)'
374
                );
375
                // If related resource set is empty for an entry then we should
376
                // not throw error instead response must be empty feed or empty links
377
                if ($request->isLinkUri()) {
378
                    $odataModelInstance = $objectModelSerializer->writeUrlElements($entryObjects);
379
                    self::assert(
380
                        $odataModelInstance instanceof \POData\ObjectModel\ODataURLCollection,
381
                        '$odataModelInstance instanceof ODataURLCollection'
382
                    );
383
                } else {
384
                    if (!empty($request->getParts())) {
385
                        foreach ($request->getParts() as $key => $part) {
386
                            $odataModelInstance = $objectModelSerializer->writeTopLevelElements($entryObjects[$key]);
387
                            self::assert(
388
                                $odataModelInstance instanceof \POData\ObjectModel\ODataFeed,
389
                                '$odataModelInstance instanceof ODataFeed'
390
                            );
391
                        }
392
                    } else {
393
                        $odataModelInstance = $objectModelSerializer->writeTopLevelElements($entryObjects);
394
                        self::assert(
395
                            $odataModelInstance instanceof \POData\ObjectModel\ODataFeed,
396
                            '$odataModelInstance instanceof ODataFeed'
397
                        );
398
                    }
399
                }
400
            } else {
401
                // Code path for entry, complex, bag, resource reference link,
402
                // primitive type or primitive value
403
                $result = $request->getTargetResult();
404
                $requestTargetKind = $request->getTargetKind();
405
                if ($request->isLinkUri()) {
406
                    // In the query 'Orders(1245)/$links/Customer', the targeted
407
                    // Customer might be null
408
                    if (is_null($result)) {
409
                        throw ODataException::createResourceNotFoundError(
410
                            $request->getIdentifier()
411
                        );
412
                    }
413
414
                    $odataModelInstance = $objectModelSerializer->writeUrlElement($result);
415
                } else if ($requestTargetKind == TargetKind::RESOURCE()) {
416
                    if (!is_null($this->_serviceHost->getRequestIfMatch())
0 ignored issues
show
introduced by
The condition is_null($this->_serviceHost->getRequestIfMatch()) is always false.
Loading history...
417
                        && !is_null($this->_serviceHost->getRequestIfNoneMatch())
0 ignored issues
show
introduced by
The condition is_null($this->_serviceH...etRequestIfNoneMatch()) is always false.
Loading history...
418
                    ) {
419
                        throw ODataException::createBadRequestError(
420
                            Messages::bothIfMatchAndIfNoneMatchHeaderSpecified()
421
                        );
422
                    }
423
                    // handle entry resource
424
                    $needToSerializeResponse = true;
425
                    $targetResourceType = $request->getTargetResourceType();
426
                    $eTag = $this->compareETag(
427
                        $result,
428
                        $targetResourceType,
429
                        $needToSerializeResponse
430
                    );
431
432
                    if ($needToSerializeResponse) {
433
                        if (is_null($result)) {
434
                            // In the query 'Orders(1245)/Customer', the targeted
435
                            // Customer might be null
436
                            // set status code to 204 => 'No Content'
437
                            $this->_serviceHost->setResponseStatusCode(HttpStatus::CODE_NOCONTENT);
438
                            $hasResponseBody = false;
439
                        } else {
440
                            $odataModelInstance = $objectModelSerializer->writeTopLevelElement($result);
441
                        }
442
                    } else {
443
                        // Resource is not modified so set status code
444
                        // to 304 => 'Not Modified'
445
                        $this->_serviceHost->setResponseStatusCode(HttpStatus::CODE_NOT_MODIFIED);
446
                        $hasResponseBody = false;
447
                    }
448
449
                    // if resource has eTagProperty then eTag header needs to written
450
                    if (!is_null($eTag)) {
451
                        $this->_serviceHost->setResponseETag($eTag);
452
                    }
453
                } else if ($requestTargetKind == TargetKind::COMPLEX_OBJECT()) {
454
455
                    $odataModelInstance = $objectModelSerializer->writeTopLevelComplexObject(
456
                        $result,
457
                        $request->getProjectedProperty()->getName(),
458
                        $request->getTargetResourceType()
0 ignored issues
show
Bug introduced by
$request->getTargetResourceType() cannot be passed to POData\ObjectModel\Objec...TopLevelComplexObject() as the parameter $resourceType expects a reference. ( Ignorable by Annotation )

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

458
                        /** @scrutinizer ignore-type */ $request->getTargetResourceType()
Loading history...
459
                    );
460
                } else if ($requestTargetKind == TargetKind::BAG()) {
461
                    $odataModelInstance = $objectModelSerializer->writeTopLevelBagObject(
462
                        $result,
463
                        $request->getProjectedProperty()->getName(),
464
                        $request->getTargetResourceType(),
465
                        $odataModelInstance
0 ignored issues
show
Unused Code introduced by
The call to POData\ObjectModel\Objec...riteTopLevelBagObject() has too many arguments starting with $odataModelInstance. ( Ignorable by Annotation )

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

465
                    /** @scrutinizer ignore-call */ 
466
                    $odataModelInstance = $objectModelSerializer->writeTopLevelBagObject(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
466
                    );
467
                } else if ($requestTargetKind == TargetKind::PRIMITIVE()) {
468
                    $odataModelInstance = $objectModelSerializer->writeTopLevelPrimitive(
469
                        $result,
470
                        $request->getProjectedProperty(),
0 ignored issues
show
Bug introduced by
$request->getProjectedProperty() cannot be passed to POData\ObjectModel\Objec...riteTopLevelPrimitive() as the parameter $resourceProperty expects a reference. ( Ignorable by Annotation )

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

470
                        /** @scrutinizer ignore-type */ $request->getProjectedProperty(),
Loading history...
471
                        $odataModelInstance
0 ignored issues
show
Unused Code introduced by
The call to POData\ObjectModel\Objec...riteTopLevelPrimitive() has too many arguments starting with $odataModelInstance. ( Ignorable by Annotation )

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

471
                    /** @scrutinizer ignore-call */ 
472
                    $odataModelInstance = $objectModelSerializer->writeTopLevelPrimitive(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
472
                    );
473
                } else if ($requestTargetKind == TargetKind::PRIMITIVE_VALUE()) {
474
                    // Code path for primitive value (Since its primitve no need for
475
                    // object model serialization)
476
                    // Customers('ANU')/CompanyName/$value => string
477
                    // Employees(1)/Photo/$value => binary stream
478
                    // Customers/$count => string
479
                } else {
480
                    self::assert(false, 'Unexpected resource target kind');
481
                }
482
            }
483
        }
484
485
        //Note: Response content type can be null for named stream
486
        if ($hasResponseBody && !is_null($responseContentType)) {
0 ignored issues
show
introduced by
The condition is_null($responseContentType) is always false.
Loading history...
487
            if ($request->getTargetKind() != TargetKind::MEDIA_RESOURCE() && $responseContentType != MimeTypes::MIME_APPLICATION_OCTETSTREAM) {
488
                //append charset for everything except:
489
                //stream resources as they have their own content type
490
                //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
491
                //
492
                $responseContentType .= ';charset=utf-8';
493
            }
494
        }
495
496
        if ($hasResponseBody) {
0 ignored issues
show
introduced by
The condition $hasResponseBody is always true.
Loading history...
497
            ResponseWriter::write(
498
                $this,
499
                $request,
500
                $odataModelInstance,
501
                $responseContentType
502
            );
503
        }
504
    }
505
506
    /**
507
     * Gets the response format for the requested resource.
508
     *
509
     * @param RequestDescription $request The request submitted by client and it's execution result.
510
     * @param UriProcessor $uriProcessor The reference to the UriProcessor.
511
     * @param IService $service Reference to the service implementation instance
512
     *
513
     * @return string the response content-type, a null value means the requested resource
514
     * is named stream and IDSSP2::getStreamContentType returned null
515
     *
516
     * @throws ODataException, HttpHeaderFailure
517
     */
518
    public static function getResponseContentType(
519
        RequestDescription $request,
520
        UriProcessor $uriProcessor,
521
        IService $service
522
    ) {
523
524
        // The Accept request-header field specifies media types which are acceptable for the response
525
526
        $host = $service->getHost();
527
        $requestAcceptText = $host->getRequestAccept();
528
        $requestVersion = $request->getResponseVersion();
529
530
        //if the $format header is present it overrides the accepts header
531
        $format = $host->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_FORMAT);
532
        if (!is_null($format)) {
533
534
            //There's a strange edge case..if application/json is supplied and it's V3
535
            if ($format == MimeTypes::MIME_APPLICATION_JSON && $requestVersion == Version::v3()) {
536
                //then it's actual minimalmetadata
537
                //TODO: should this be done with the header text too?
538
                $format = MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META;
539
            }
540
541
            $requestAcceptText = ServiceHost::translateFormatToMime($requestVersion, $format);
542
        }
543
544
545
546
547
        //The response format can be dictated by the target resource kind. IE a $value will be different then expected
548
        //getTargetKind doesn't deal with link resources directly and this can change things
549
        $targetKind = $request->isLinkUri() ? TargetKind::LINK() : $request->getTargetKind();
550
551
        switch ($targetKind) {
552
            case TargetKind::METADATA():
553
                return HttpProcessUtility::selectMimeType(
554
                    $requestAcceptText,
555
                    array(MimeTypes::MIME_APPLICATION_XML)
556
                );
557
558
            case TargetKind::SERVICE_DIRECTORY():
559
                return HttpProcessUtility::selectMimeType(
560
                    $requestAcceptText,
561
                    array(
562
                        MimeTypes::MIME_APPLICATION_XML,
563
                        MimeTypes::MIME_APPLICATION_ATOMSERVICE,
564
                        MimeTypes::MIME_APPLICATION_JSON,
565
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
566
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
567
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
568
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
569
570
                    )
571
                );
572
573
            case TargetKind::PRIMITIVE_VALUE():
574
                $supportedResponseMimeTypes = array(MimeTypes::MIME_TEXTPLAIN);
575
576
                if ($request->getIdentifier() != '$count') {
577
                    $projectedProperty = $request->getProjectedProperty();
578
                    self::assert(
579
                        !is_null($projectedProperty),
580
                        '!is_null($projectedProperty)'
581
                    );
582
                    $type = $projectedProperty->getInstanceType();
583
                    self::assert(
584
                        !is_null($type) && $type instanceof IType,
585
                        '!is_null($type) && $type instanceof IType'
586
                    );
587
                    if ($type instanceof Binary) {
588
                        $supportedResponseMimeTypes = array(MimeTypes::MIME_APPLICATION_OCTETSTREAM);
589
                    }
590
                }
591
592
                return HttpProcessUtility::selectMimeType(
593
                    $requestAcceptText,
594
                    $supportedResponseMimeTypes
595
                );
596
597
            case TargetKind::PRIMITIVE():
598
            case TargetKind::COMPLEX_OBJECT():
599
            case TargetKind::BAG():
600
            case TargetKind::LINK():
601
                return HttpProcessUtility::selectMimeType(
602
                    $requestAcceptText,
603
                    array(
604
                        MimeTypes::MIME_APPLICATION_XML,
605
                        MimeTypes::MIME_TEXTXML,
606
                        MimeTypes::MIME_APPLICATION_JSON,
607
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
608
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
609
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
610
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
611
                    )
612
                );
613
614
            case TargetKind::BATCH():
615
            case TargetKind::RESOURCE():
616
                return HttpProcessUtility::selectMimeType(
617
                    $requestAcceptText,
618
                    array(
619
                        MimeTypes::MIME_APPLICATION_ATOM,
620
                        MimeTypes::MIME_APPLICATION_JSON,
621
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
622
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
623
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
624
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
625
                    )
626
                );
627
628
            case TargetKind::MEDIA_RESOURCE():
629
                if (!$request->isNamedStream() && !$request->getTargetResourceType()->isMediaLinkEntry()) {
630
                    throw ODataException::createBadRequestError(
631
                        Messages::badRequestInvalidUriForMediaResource(
632
                            $host->getAbsoluteRequestUri()->getUrlAsString()
633
                        )
634
                    );
635
                }
636
637
                $uriProcessor->execute();
638
                $request->setExecuted();
639
                // DSSW::getStreamContentType can throw error in 2 cases
640
                // 1. If the required stream implementation not found
641
                // 2. If IDSSP::getStreamContentType returns NULL for MLE
642
                $responseContentType = $service->getStreamProviderWrapper()
643
                    ->getStreamContentType(
644
                        $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

644
                        /** @scrutinizer ignore-type */ $request->getTargetResult(),
Loading history...
645
                        $request->getResourceStreamInfo()
646
                    );
647
648
649
                // Note StreamWrapper::getStreamContentType can return NULL if the requested named stream has not
650
                // yet been uploaded. But for an MLE if IDSSP::getStreamContentType returns NULL then StreamWrapper will throw error
651
                if (!is_null($responseContentType)) {
0 ignored issues
show
introduced by
The condition is_null($responseContentType) is always false.
Loading history...
652
                    $responseContentType = HttpProcessUtility::selectMimeType(
653
                        $requestAcceptText,
654
                        array($responseContentType)
655
                    );
656
                }
657
658
                return $responseContentType;
659
        }
660
661
662
        //If we got here, we just don't know what it is...
663
        throw new ODataException(Messages::unsupportedMediaType(), 415);
664
665
    }
666
667
668
669
    /**
670
     * For the given entry object compare it's eTag (if it has eTag properties)
671
     * with current eTag request headers (if it present).
672
     *
673
     * @param mixed        &$entryObject             entity resource for which etag
674
     *                                               needs to be checked.
675
     * @param ResourceType &$resourceType            Resource type of the entry
676
     *                                               object.
677
     * @param boolean      &$needToSerializeResponse On return, this will contain
678
     *                                               True if response needs to be
679
     *                                               serialized, False otherwise.
680
     * @param boolean $needToSerializeResponse
681
     *
682
     * @return string|null The ETag for the entry object if it has eTag properties
683
     *                     NULL otherwise.
684
     */
685
    protected function compareETag(&$entryObject, ResourceType &$resourceType,
686
        &$needToSerializeResponse
687
    ) {
688
        $needToSerializeResponse = true;
689
        $eTag = null;
690
        $ifMatch = $this->_serviceHost->getRequestIfMatch();
691
        $ifNoneMatch = $this->_serviceHost->getRequestIfNoneMatch();
692
        if (is_null($entryObject)) {
693
            if (!is_null($ifMatch)) {
0 ignored issues
show
introduced by
The condition is_null($ifMatch) is always false.
Loading history...
694
                throw ODataException::createPreConditionFailedError(
695
                    Messages::eTagNotAllowedForNonExistingResource()
696
                );
697
            }
698
699
            return null;
700
        }
701
702
        if ($this->config->getValidateETagHeader() && !$resourceType->hasETagProperties()) {
703
            if (!is_null($ifMatch) || !is_null($ifNoneMatch)) {
0 ignored issues
show
introduced by
The condition is_null($ifMatch) is always false.
Loading history...
704
                // No eTag properties but request has eTag headers, bad request
705
                throw ODataException::createBadRequestError(
706
                    Messages::noETagPropertiesForType()
707
                );
708
            }
709
710
            // We need write the response but no eTag header
711
            return null;
712
        }
713
714
        if (!$this->config->getValidateETagHeader()) {
715
            // Configuration says do not validate ETag so we will not write ETag header in the
716
            // response even though the requested resource support it
717
            return null;
718
        }
719
720
        if (is_null($ifMatch) && is_null($ifNoneMatch)) {
0 ignored issues
show
introduced by
The condition is_null($ifMatch) is always false.
Loading history...
721
            // No request eTag header, we need to write the response
722
            // and eTag header
723
        } else if (strcmp($ifMatch, '*') == 0) {
724
            // If-Match:* => we need to write the response and eTag header
725
        } else if (strcmp($ifNoneMatch, '*') == 0) {
726
            // if-None-Match:* => Do not write the response (304 not modified),
727
            // but write eTag header
728
            $needToSerializeResponse = false;
729
        } else {
730
            $eTag = $this->getETagForEntry($entryObject, $resourceType);
731
            // Note: The following code for attaching the prefix W\"
732
            // and the suffix " can be done in getETagForEntry function
733
            // but that is causing an issue in Linux env where the
734
            // firefix browser is unable to parse the ETag in this case.
735
            // Need to follow up PHP core devs for this.
736
            $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
737
            if (!is_null($ifMatch)) {
0 ignored issues
show
introduced by
The condition is_null($ifMatch) is always false.
Loading history...
738
                if (strcmp($eTag, $ifMatch) != 0) {
739
                    // Requested If-Match value does not match with current
740
                    // eTag Value then pre-condition error
741
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
742
                    throw ODataException::createPreConditionFailedError(
743
                        Messages::eTagValueDoesNotMatch()
744
                    );
745
                }
746
            } else if (strcmp($eTag, $ifNoneMatch) == 0) {
747
                //304 not modified, but in write eTag header
748
                $needToSerializeResponse = false;
749
            }
750
        }
751
752
        if (is_null($eTag)) {
753
            $eTag = $this->getETagForEntry($entryObject, $resourceType);
754
            // Note: The following code for attaching the prefix W\"
755
            // and the suffix " can be done in getETagForEntry function
756
            // but that is causing an issue in Linux env where the
757
            // firefix browser is unable to parse the ETag in this case.
758
            // Need to follow up PHP core devs for this.
759
            $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
760
        }
761
762
        return $eTag;
763
    }
764
765
    /**
766
     * Returns the etag for the given resource.
767
     * Note: This function will not add W\" prefix and " suffix, its callers
768
     * repsonsability.
769
     *
770
     * @param mixed        &$entryObject  Resource for which etag value needs to
771
     *                                    be returned
772
     * @param ResourceType &$resourceType Resource type of the $entryObject
773
     *
774
     * @return string|null ETag value for the given resource (with values encoded
775
     *                     for use in a URI) there are etag properties, NULL if
776
     *                     there is no etag property.
777
     */
778
    protected function getETagForEntry(&$entryObject, ResourceType &$resourceType)
779
    {
780
        $eTag = null;
781
        $comma = null;
782
        foreach ($resourceType->getETagProperties() as $eTagProperty) {
783
            $type = $eTagProperty->getInstanceType();
784
            self::assert(
785
                !is_null($type) && $type instanceof IType,
786
                '!is_null($type) && $type instanceof IType'
787
            );
788
789
            $value = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $value is dead and can be removed.
Loading history...
790
            try {
791
792
                //TODO #88...also this seems like dupe work
793
                $reflectionProperty = new \ReflectionProperty($entryObject, $eTagProperty->getName());
794
                $value = $reflectionProperty->getValue($entryObject);
795
            } catch (\ReflectionException $reflectionException) {
796
                throw ODataException::createInternalServerError(
797
                    Messages::failedToAccessProperty($eTagProperty->getName(), $resourceType->getName())
798
                );
799
            }
800
801
            if (is_null($value)) {
802
                $eTag = $eTag . $comma . 'null';
803
            } else {
804
                $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

804
                $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...
805
            }
806
807
            $comma = ',';
808
        }
809
810
        if (!is_null($eTag)) {
811
            // If eTag is made up of datetime or string properties then the above
812
            // IType::convertToOData will perform utf8 and url encode. But we don't
813
            // want this for eTag value.
814
            $eTag = urldecode(utf8_decode($eTag));
815
            return rtrim($eTag, ',');
816
        }
817
818
        return null;
819
    }
820
821
    /**
822
     * This function will perform the following operations:
823
     * (1) Invoke delegateRequestProcessing method to process the request based
824
     *     on request method (GET, PUT/MERGE, POST, DELETE)
825
     * (3) If the result of processing of request needs to be serialized as HTTP
826
     *     response body (e.g. GET request result in single resource or resource
827
     *     collection, successful POST operation for an entity need inserted
828
     *     entity to be serialized back etc..), Serialize the result by using
829
     *     'serializeReultForResponseBody' method
830
     *     Set the serialized result to
831
     *     WebOperationContext::Current()::OutgoingWebResponseContext::Stream.
832
     *
833
     *     @return void
834
     */
835
    protected function handleRequest2()
836
    {
837
    }
838
839
    /**
840
     * This method will perform the following operations:
841
     * (1) If request method is GET, then result is already there in the
842
     *     RequestDescription so simply return the RequestDescription
843
     * (2). If request method is for CDU
844
     *      (Create/Delete/Update - POST/DELETE/PUT-MERGE) hand
845
     *      over the responsibility to respective handlers. The handler
846
     *      methods are:
847
     *      (a) handlePOSTOperation() => POST
848
     *      (b) handlePUTOperation() => PUT/MERGE
849
     *      (c) handleDELETEOperation() => DELETE
850
     * (3). Check whether its required to write any result to the response
851
     *      body
852
     *      (a). Request method is GET
853
     *      (b). Request is a POST for adding NEW Entry
854
     *      (c). Request is a POST for adding Media Resource Stream
855
     *      (d). Request is a POST for adding a link
856
     *      (e). Request is a DELETE for deleting entry or relationship
857
     *      (f). Request is a PUT/MERGE for updating an entry
858
     *      (g). Request is a PUT for updating a link
859
     *     In case a, b and c we need to write the result to response body,
860
     *     for d, e, f and g no body content.
861
     *
862
     * @return RequestDescription|null Instance of RequestDescription with
863
     *         result to be write back Null if no result to write.
864
     */
865
    protected function delegateRequestProcessing()
866
    {
867
    }
868
869
    /**
870
     * Serialize the result in the current request description using
871
     * appropriate odata writer (AtomODataWriter/JSONODataWriter)
872
     *
873
     * @return void
874
     *
875
     */
876
    protected function serializeReultForResponseBody()
877
    {
878
    }
879
880
    /**
881
     * Handle POST request.
882
     *
883
     * @return void
884
     *
885
     * @throws NotImplementedException
886
     */
887
    protected function handlePOSTOperation()
888
    {
889
    }
890
891
    /**
892
     * Handle PUT/MERGE request.
893
     *
894
     * @return void
895
     *
896
     * @throws NotImplementedException
897
     */
898
    protected function handlePUTOperation()
899
    {
900
    }
901
902
    /**
903
     * Handle DELETE request.
904
     *
905
     * @return void
906
     *
907
     * @throws NotImplementedException
908
     */
909
    protected function handleDELETEOperation()
910
    {
911
    }
912
913
    /**
914
     * Assert that the given condition is true.
915
     *
916
     * @param boolean $condition         The condtion to check.
917
     * @param string  $conditionAsString Message to show if assertion fails.
918
     *
919
     * @return void
920
     *
921
     * @throws InvalidOperationException
922
     */
923
    protected static function assert($condition, $conditionAsString)
924
    {
925
        if (!$condition) {
926
            throw new InvalidOperationException(
927
                "Unexpected state, expecting $conditionAsString"
928
            );
929
        }
930
    }
931
}
932