Test Setup Failed
Push — master ( cd6ee4...9894d0 )
by Alex
53s
created

BaseService::serializeResult()   F

Complexity

Conditions 31
Paths 337

Size

Total Lines 164
Code Lines 92

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 164
rs 3.4536
c 0
b 0
f 0
cc 31
eloc 92
nc 337
nop 2

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 POData\Common\ErrorHandler;
6
use POData\Common\HttpStatus;
7
use POData\Common\Messages;
8
use POData\Common\MimeTypes;
9
use POData\Common\NotImplementedException;
10
use POData\Common\ODataConstants;
11
use POData\Common\ODataException;
12
use POData\Common\ReflectionHandler;
13
use POData\Common\Version;
14
use POData\Configuration\IServiceConfiguration;
15
use POData\Configuration\ServiceConfiguration;
16
use POData\ObjectModel\IObjectSerialiser;
17
use POData\ObjectModel\ObjectModelSerializer;
18
use POData\ObjectModel\ODataFeed;
19
use POData\ObjectModel\ODataURLCollection;
20
use POData\OperationContext\HTTPRequestMethod;
21
use POData\OperationContext\IOperationContext;
22
use POData\OperationContext\ServiceHost;
23
use POData\Providers\Metadata\IMetadataProvider;
24
use POData\Providers\Metadata\ResourceType;
25
use POData\Providers\Metadata\Type\Binary;
26
use POData\Providers\Metadata\Type\IType;
27
use POData\Providers\ProvidersWrapper;
28
use POData\Providers\Query\IQueryProvider;
29
use POData\Providers\Query\QueryResult;
30
use POData\Providers\Stream\StreamProviderWrapper;
31
use POData\UriProcessor\RequestDescription;
32
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
33
use POData\UriProcessor\UriProcessor;
34
use POData\Writers\Atom\AtomODataWriter;
35
use POData\Writers\Json\JsonLightMetadataLevel;
36
use POData\Writers\Json\JsonLightODataWriter;
37
use POData\Writers\Json\JsonODataV1Writer;
38
use POData\Writers\Json\JsonODataV2Writer;
39
use POData\Writers\ODataWriterRegistry;
40
use POData\Writers\ResponseWriter;
41
42
/**
43
 * Class BaseService.
44
 *
45
 * The base class for all BaseService specific classes. This class implements
46
 * the following interfaces:
47
 *  (1) IRequestHandler
48
 *      Implementing this interface requires defining the function
49
 *      'handleRequest' that will be invoked by dispatcher
50
 *  (2) IService
51
 *      Force BaseService class to implement functions for custom
52
 *      data service providers
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
    protected $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
     * 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 IServiceConfiguration
85
     */
86
    protected $config;
87
88
    /**
89
     * Hold reference to object serialiser - bit wot turns PHP objects
90
     * into message traffic on wire.
91
     *
92
     * @var IObjectSerialiser
93
     */
94
    protected $objectSerialiser;
95
96
    /**
97
     * Get reference to object serialiser - bit wot turns PHP objects
98
     * into message traffic on wire.
99
     *
100
     * @var IObjectSerialiser
101
     */
102
    public function getObjectSerialiser()
103
    {
104
        assert(null != $this->objectSerialiser);
105
106
        return $this->objectSerialiser;
107
    }
108
109
    protected function __construct(IObjectSerialiser $serialiser = null)
110
    {
111
        if (null != $serialiser) {
112
            $serialiser->setService($this);
113
        } else {
114
            $serialiser = new ObjectModelSerializer($this, null);
115
        }
116
        $this->objectSerialiser = $serialiser;
117
    }
118
119
    /**
120
     * Gets reference to ServiceConfiguration instance so that
121
     * service specific rules defined by the developer can be
122
     * accessed.
123
     *
124
     * @return IServiceConfiguration
125
     */
126
    public function getConfiguration()
127
    {
128
        assert(null != $this->config);
129
130
        return $this->config;
131
    }
132
133
    //TODO: shouldn't we hide this from the interface..if we need it at all.
134
135
    /**
136
     * Get the wrapper over developer's IQueryProvider and IMetadataProvider implementation.
137
     *
138
     * @return ProvidersWrapper
139
     */
140
    public function getProvidersWrapper()
141
    {
142
        return $this->providersWrapper;
143
    }
144
145
    /**
146
     * Gets reference to wrapper class instance over IDSSP implementation.
147
     *
148
     * @return StreamProviderWrapper
149
     */
150
    public function getStreamProviderWrapper()
151
    {
152
        return $this->streamProvider;
153
    }
154
155
    /**
156
     * Get reference to the data service host instance.
157
     *
158
     * @return ServiceHost
159
     */
160
    public function getHost()
161
    {
162
        assert(null != $this->_serviceHost);
163
164
        return $this->_serviceHost;
165
    }
166
167
    /**
168
     * Sets the data service host instance.
169
     *
170
     * @param ServiceHost $serviceHost The data service host instance
171
     */
172
    public function setHost(ServiceHost $serviceHost)
173
    {
174
        $this->_serviceHost = $serviceHost;
175
    }
176
177
    /**
178
     * To get reference to operation context where we have direct access to
179
     * headers and body of Http Request, we have received and the Http Response
180
     * We are going to send.
181
     *
182
     * @return IOperationContext
183
     */
184
    public function getOperationContext()
185
    {
186
        return $this->getHost()->getOperationContext();
187
    }
188
189
    /**
190
     * Get reference to the wrapper over IStreamProvider or
191
     * IStreamProvider2 implementations.
192
     *
193
     * @return StreamProviderWrapper
194
     */
195
    public function getStreamProvider()
196
    {
197
        if (is_null($this->streamProvider)) {
198
            $this->streamProvider = new StreamProviderWrapper();
199
            $this->streamProvider->setService($this);
200
        }
201
202
        return $this->streamProvider;
203
    }
204
205
    /**
206
     * Top-level handler invoked by Dispatcher against any request to this
207
     * service. This method will hand over request processing task to other
208
     * functions which process the request, set required headers and Response
209
     * stream (if any in Atom/Json format) in
210
     * WebOperationContext::Current()::OutgoingWebResponseContext.
211
     * Once this function returns, dispatcher uses global WebOperationContext
212
     * to write out the request response to client.
213
     * This function will perform the following operations:
214
     * (1) Check whether the top level service class implements
215
     *     IServiceProvider which means the service is a custom service, in
216
     *     this case make sure the top level service class implements
217
     *     IMetaDataProvider and IQueryProvider.
218
     *     These are the minimal interfaces that a custom service to be
219
     *     implemented in order to expose its data as OData. Save reference to
220
     *     These interface implementations.
221
     *     NOTE: Here we will ensure only providers for IDSQP and IDSMP. The
222
     *     IDSSP will be ensured only when there is an GET request on MLE/Named
223
     *     stream.
224
     *
225
     * (2). Invoke 'Initialize' method of top level service for
226
     *      collecting the configuration rules set by the developer for this
227
     *      service.
228
     *
229
     * (3). Invoke the Uri processor to process the request URI. The uri
230
     *      processor will do the following:
231
     *      (a). Validate the request uri syntax using OData uri rules
232
     *      (b). Validate the request using metadata of this service
233
     *      (c). Parse the request uri and using, IQueryProvider
234
     *           implementation, fetches the resources pointed by the uri
235
     *           if required
236
     *      (d). Build a RequestDescription which encapsulate everything
237
     *           related to request uri (e.g. type of resource, result
238
     *           etc...)
239
     * (3). Invoke handleRequest2 for further processing
240
     */
241
    public function handleRequest()
242
    {
243
        try {
244
            $this->createProviders();
245
            $this->getHost()->validateQueryParameters();
246
            //$requestMethod = $this->getOperationContext()->incomingRequest()->getMethod();
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
247
            //if ($requestMethod != HTTPRequestMethod::GET()) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
248
            // Now supporting GET and trying to support PUT
249
            //throw ODataException::createNotImplementedError(Messages::onlyReadSupport($requestMethod));
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
250
            //}
251
252
            $uriProcessor = UriProcessor::process($this);
253
            $request = $uriProcessor->getRequest();
254
            $this->serializeResult($request, $uriProcessor);
255
        } catch (\Exception $exception) {
256
            ErrorHandler::handleException($exception, $this);
257
            // Return to dispatcher for writing serialized exception
258
            return;
259
        }
260
    }
261
262
    /**
263
     * @return IQueryProvider
264
     */
265
    abstract public function getQueryProvider();
266
267
    /**
268
     * @return IMetadataProvider
269
     */
270
    abstract public function getMetadataProvider();
271
272
    /**
273
     *  @return \POData\Providers\Stream\IStreamProvider2
274
     */
275
    abstract public function getStreamProviderX();
276
277
    /** @var ODataWriterRegistry */
278
    protected $writerRegistry;
279
280
    /**
281
     * Returns the ODataWriterRegistry to use when writing the response to a service document or resource request.
282
     *
283
     * @return ODataWriterRegistry
284
     */
285
    public function getODataWriterRegistry()
286
    {
287
        assert(null != $this->writerRegistry);
288
289
        return $this->writerRegistry;
290
    }
291
292
    /**
293
     * This method will query and validates for IMetadataProvider and IQueryProvider implementations, invokes
294
     * BaseService::Initialize to initialize service specific policies.
295
     *
296
     * @throws ODataException
297
     */
298
    protected function createProviders()
299
    {
300
        $metadataProvider = $this->getMetadataProvider();
301
        if (is_null($metadataProvider)) {
302
            throw ODataException::createInternalServerError(Messages::providersWrapperNull());
303
        }
304
305
        if (!$metadataProvider instanceof IMetadataProvider) {
306
            throw ODataException::createInternalServerError(Messages::invalidMetadataInstance());
307
        }
308
309
        $queryProvider = $this->getQueryProvider();
310
311
        if (is_null($queryProvider)) {
312
            throw ODataException::createInternalServerError(Messages::providersWrapperNull());
313
        }
314
315
        if (!$queryProvider instanceof IQueryProvider) {
316
            throw ODataException::createInternalServerError(Messages::invalidQueryInstance());
317
        }
318
319
        $this->config = new ServiceConfiguration($metadataProvider);
320
        $this->providersWrapper = new ProvidersWrapper(
321
            $metadataProvider,
322
            $queryProvider,
323
            $this->config
324
        );
325
326
        $this->initialize($this->config);
327
328
        //TODO: this seems like a bad spot to do this
329
        $this->writerRegistry = new ODataWriterRegistry();
330
        $this->registerWriters();
331
    }
332
333
    //TODO: i don't want this to be public..but it's the only way to test it right now...
334
    public function registerWriters()
335
    {
336
        $registry = $this->getODataWriterRegistry();
337
        $serviceVersion = $this->getConfiguration()->getMaxDataServiceVersion();
338
        $serviceURI = $this->getHost()->getAbsoluteServiceUri()->getUrlAsString();
339
340
        //We always register the v1 stuff
341
        $registry->register(new JsonODataV1Writer());
342
        $registry->register(new AtomODataWriter($serviceURI));
343
344
        if (-1 < $serviceVersion->compare(Version::v2())) {
345
            $registry->register(new JsonODataV2Writer());
346
        }
347
348
        if (-1 < $serviceVersion->compare(Version::v3())) {
349
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::NONE(), $serviceURI));
350
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::MINIMAL(), $serviceURI));
351
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::FULL(), $serviceURI));
352
        }
353
    }
354
355
    /**
356
     * Serialize the requested resource.
357
     *
358
     * @param RequestDescription $request      The description of the request  submitted by the client
359
     * @param UriProcessor       $uriProcessor Reference to the uri processor
360
     */
361
    protected function serializeResult(RequestDescription $request, UriProcessor $uriProcessor)
362
    {
363
        $isETagHeaderAllowed = $request->isETagHeaderAllowed();
364
365
        if ($this->getConfiguration()->getValidateETagHeader() && !$isETagHeaderAllowed) {
366
            if (!is_null($this->getHost()->getRequestIfMatch())
367
                || !is_null($this->getHost()->getRequestIfNoneMatch())
368
            ) {
369
                throw ODataException::createBadRequestError(
370
                    Messages::eTagCannotBeSpecified($this->getHost()->getAbsoluteRequestUri()->getUrlAsString())
371
                );
372
            }
373
        }
374
375
        $responseContentType = $this->getResponseContentType($request, $uriProcessor);
376
377
        if (is_null($responseContentType) && $request->getTargetKind() != TargetKind::MEDIA_RESOURCE()) {
378
            //the responseContentType can ONLY be null if it's a stream (media resource) and
379
            // that stream is storing null as the content type
380
            throw new ODataException(Messages::unsupportedMediaType(), 415);
381
        }
382
383
        $odataModelInstance = null;
384
        $hasResponseBody = true;
385
        // Execution required at this point if request points to any resource other than
386
387
        // (1) media resource - For Media resource 'getResponseContentType' already performed execution as
388
        // it needs to know the mime type of the stream
389
        // (2) metadata - internal resource
390
        // (3) service directory - internal resource
391
        if ($request->needExecution()) {
392
            $method = $this->getHost()->getOperationContext()->incomingRequest()->getMethod();
393
            $uriProcessor->execute();
394
            if (HTTPRequestMethod::DELETE() == $method) {
395
                $this->getHost()->setResponseStatusCode(HttpStatus::CODE_OK);
396
397
                return;
398
            }
399
400
            $objectModelSerializer = $this->getObjectSerialiser();
401
            $objectModelSerializer->setRequest($request);
402
403
            $targetResourceType = $request->getTargetResourceType();
404
            assert(null != $targetResourceType, 'Target resource type cannot be null');
405
406
            $method = (HTTPRequestMethod::POST() != $method);
407
            if (!$request->isSingleResult() && $method) {
408
                // Code path for collection (feed or links)
409
                $entryObjects = $request->getTargetResult();
410
                assert($entryObjects instanceof QueryResult, '!$entryObjects instanceof QueryResult');
411
                assert(is_array($entryObjects->results), '!is_array($entryObjects->results)');
412
                // If related resource set is empty for an entry then we should
413
                // not throw error instead response must be empty feed or empty links
414
                if ($request->isLinkUri()) {
415
                    $odataModelInstance = $objectModelSerializer->writeUrlElements($entryObjects);
416
                    assert(
417
                        $odataModelInstance instanceof ODataURLCollection,
418
                        '!$odataModelInstance instanceof ODataURLCollection'
419
                    );
420
                } else {
421
                    $odataModelInstance = $objectModelSerializer->writeTopLevelElements($entryObjects);
422
                    assert($odataModelInstance instanceof ODataFeed, '!$odataModelInstance instanceof ODataFeed');
423
                }
424
            } else {
425
                // Code path for entity, complex, bag, resource reference link,
426
                // primitive type or primitive value
427
                $result = $request->getTargetResult();
428
                if (!$result instanceof QueryResult) {
429
                    $result = new QueryResult();
430
                    $result->results = $request->getTargetResult();
431
                }
432
                $requestTargetKind = $request->getTargetKind();
433
                $requestProperty = $request->getProjectedProperty();
434
                if ($request->isLinkUri()) {
435
                    // In the query 'Orders(1245)/$links/Customer', the targeted
436
                    // Customer might be null
437
                    if (is_null($result->results)) {
438
                        throw ODataException::createResourceNotFoundError($request->getIdentifier());
439
                    }
440
                    $odataModelInstance = $objectModelSerializer->writeUrlElement($result);
441
                } elseif (TargetKind::RESOURCE() == $requestTargetKind
442
                          || TargetKind::SINGLETON() == $requestTargetKind) {
443
                    if (!is_null($this->getHost()->getRequestIfMatch())
444
                        && !is_null($this->getHost()->getRequestIfNoneMatch())
445
                    ) {
446
                        throw ODataException::createBadRequestError(
447
                            Messages::bothIfMatchAndIfNoneMatchHeaderSpecified()
448
                        );
449
                    }
450
                    // handle entry resource
451
                    $needToSerializeResponse = true;
452
                    $eTag = $this->compareETag($result, $targetResourceType, $needToSerializeResponse);
453
454
                    if ($needToSerializeResponse) {
455
                        if (is_null($result)) {
456
                            // In the query 'Orders(1245)/Customer', the targeted
457
                            // Customer might be null
458
                            // set status code to 204 => 'No Content'
459
                            $this->getHost()->setResponseStatusCode(HttpStatus::CODE_NOCONTENT);
460
                            $hasResponseBody = false;
461
                        } else {
462
                            $odataModelInstance = $objectModelSerializer->writeTopLevelElement($result);
463
                        }
464
                    } else {
465
                        // Resource is not modified so set status code
466
                        // to 304 => 'Not Modified'
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
467
                        $this->getHost()->setResponseStatusCode(HttpStatus::CODE_NOT_MODIFIED);
468
                        $hasResponseBody = false;
469
                    }
470
471
                    // if resource has eTagProperty then eTag header needs to written
472
                    if (!is_null($eTag)) {
473
                        $this->getHost()->setResponseETag($eTag);
474
                    }
475
                } elseif (TargetKind::COMPLEX_OBJECT() == $requestTargetKind) {
476
                    assert(null != $requestProperty, 'Projected request property cannot be null');
477
                    $odataModelInstance = $objectModelSerializer->writeTopLevelComplexObject(
478
                        $result,
479
                        $requestProperty->getName(),
480
                        $targetResourceType
481
                    );
482
                } elseif (TargetKind::BAG() == $requestTargetKind) {
483
                    assert(null != $requestProperty, 'Projected request property cannot be null');
484
                    $odataModelInstance = $objectModelSerializer->writeTopLevelBagObject(
485
                        $result,
486
                        $requestProperty->getName(),
487
                        $targetResourceType,
488
                        $odataModelInstance
0 ignored issues
show
Unused Code introduced by
The call to IObjectSerialiser::writeTopLevelBagObject() has too many arguments starting with $odataModelInstance.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
489
                    );
490
                } elseif (TargetKind::PRIMITIVE() == $requestTargetKind) {
491
                    $odataModelInstance = $objectModelSerializer->writeTopLevelPrimitive(
492
                        $result,
493
                        $requestProperty,
494
                        $odataModelInstance
0 ignored issues
show
Unused Code introduced by
The call to IObjectSerialiser::writeTopLevelPrimitive() has too many arguments starting with $odataModelInstance.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
495
                    );
496
                } elseif (TargetKind::PRIMITIVE_VALUE() == $requestTargetKind) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
497
                    // Code path for primitive value (Since its primitve no need for
498
                    // object model serialization)
499
                    // Customers('ANU')/CompanyName/$value => string
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
500
                    // Employees(1)/Photo/$value => binary stream
501
                    // Customers/$count => string
502
                } else {
503
                    assert(false, 'Unexpected resource target kind');
504
                }
505
            }
506
        }
507
508
        //Note: Response content type can be null for named stream
509
        if ($hasResponseBody && !is_null($responseContentType)) {
510
            if (TargetKind::MEDIA_RESOURCE() != $request->getTargetKind()
511
                && MimeTypes::MIME_APPLICATION_OCTETSTREAM != $responseContentType) {
512
                //append charset for everything except:
513
                //stream resources as they have their own content type
514
                //binary properties (they content type will be App Octet for those...is this a good way?
515
                //we could also decide based upon the projected property
516
517
                $responseContentType .= ';charset=utf-8';
518
            }
519
        }
520
521
        if ($hasResponseBody) {
522
            ResponseWriter::write($this, $request, $odataModelInstance, $responseContentType);
523
        }
524
    }
525
526
    /**
527
     * Gets the response format for the requested resource.
528
     *
529
     * @param RequestDescription $request      The request submitted by client and it's execution result
530
     * @param UriProcessor       $uriProcessor The reference to the UriProcessor
531
     *
532
     * @throws ODataException, HttpHeaderFailure
533
     *
534
     * @return string the response content-type, a null value means the requested resource
535
     *                is named stream and IDSSP2::getStreamContentType returned null
536
     */
537
    public function getResponseContentType(
538
        RequestDescription $request,
539
        UriProcessor $uriProcessor
540
    ) {
541
        $baseMimeTypes = [
542
            MimeTypes::MIME_APPLICATION_JSON,
543
            MimeTypes::MIME_APPLICATION_JSON_FULL_META,
544
            MimeTypes::MIME_APPLICATION_JSON_NO_META,
545
            MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
546
            MimeTypes::MIME_APPLICATION_JSON_VERBOSE, ];
547
548
        // The Accept request-header field specifies media types which are acceptable for the response
549
550
        $host = $this->getHost();
551
        $requestAcceptText = $host->getRequestAccept();
552
        $requestVersion = $request->getResponseVersion();
553
554
        //if the $format header is present it overrides the accepts header
555
        $format = $host->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_FORMAT);
556
        if (!is_null($format)) {
557
            //There's a strange edge case..if application/json is supplied and it's V3
558
            if (MimeTypes::MIME_APPLICATION_JSON == $format && Version::v3() == $requestVersion) {
559
                //then it's actual minimalmetadata
560
                //TODO: should this be done with the header text too?
561
                $format = MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META;
562
            }
563
564
            $requestAcceptText = ServiceHost::translateFormatToMime($requestVersion, $format);
565
        }
566
567
        //The response format can be dictated by the target resource kind. IE a $value will be different then expected
568
        //getTargetKind doesn't deal with link resources directly and this can change things
569
        $targetKind = $request->isLinkUri() ? TargetKind::LINK() : $request->getTargetKind();
570
571
        switch ($targetKind) {
572
            case TargetKind::METADATA():
573
                return HttpProcessUtility::selectMimeType(
574
                    $requestAcceptText,
575
                    [MimeTypes::MIME_APPLICATION_XML]
576
                );
577
578
            case TargetKind::SERVICE_DIRECTORY():
579
                return HttpProcessUtility::selectMimeType(
580
                    $requestAcceptText,
581
                    array_merge(
582
                        [MimeTypes::MIME_APPLICATION_ATOMSERVICE],
583
                        $baseMimeTypes
584
                    )
585
                );
586
587
            case TargetKind::PRIMITIVE_VALUE():
588
                $supportedResponseMimeTypes = [MimeTypes::MIME_TEXTPLAIN];
589
590
                if ('$count' != $request->getIdentifier()) {
591
                    $projectedProperty = $request->getProjectedProperty();
592
                    assert(!is_null($projectedProperty), 'is_null($projectedProperty)');
593
                    $type = $projectedProperty->getInstanceType();
594
                    assert($type instanceof IType, '!$type instanceof IType');
595
                    if ($type instanceof Binary) {
596
                        $supportedResponseMimeTypes = [MimeTypes::MIME_APPLICATION_OCTETSTREAM];
597
                    }
598
                }
599
600
                return HttpProcessUtility::selectMimeType(
601
                    $requestAcceptText,
602
                    $supportedResponseMimeTypes
603
                );
604
605
            case TargetKind::PRIMITIVE():
606
            case TargetKind::COMPLEX_OBJECT():
607
            case TargetKind::BAG():
608
            case TargetKind::LINK():
609
                return HttpProcessUtility::selectMimeType(
610
                    $requestAcceptText,
611
                    array_merge(
612
                        [MimeTypes::MIME_APPLICATION_XML,
613
                            MimeTypes::MIME_TEXTXML, ],
614
                        $baseMimeTypes
615
                    )
616
                );
617
618
            case TargetKind::SINGLETON():
619
            case TargetKind::RESOURCE():
620
                return HttpProcessUtility::selectMimeType(
621
                    $requestAcceptText,
622
                    array_merge(
623
                        [MimeTypes::MIME_APPLICATION_ATOM],
624
                        $baseMimeTypes
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 = $this->getStreamProviderWrapper()
643
                    ->getStreamContentType(
644
                        $request->getTargetResult(),
645
                        $request->getResourceStreamInfo()
646
                    );
647
648
                // Note StreamWrapper::getStreamContentType can return NULL if the requested named stream has not
649
                // yet been uploaded. But for an MLE if IDSSP::getStreamContentType returns NULL
650
                // then StreamWrapper will throw error
651
                if (!is_null($responseContentType)) {
652
                    $responseContentType = HttpProcessUtility::selectMimeType(
653
                        $requestAcceptText,
654
                        [$responseContentType]
655
                    );
656
                }
657
658
                return $responseContentType;
659
        }
660
661
        //If we got here, we just don't know what it is...
662
        throw new ODataException(Messages::unsupportedMediaType(), 415);
663
    }
664
665
    /**
666
     * For the given entry object compare its eTag (if it has eTag properties)
667
     * with current eTag request headers (if present).
668
     *
669
     * @param mixed        &$entryObject             entity resource for which etag
670
     *                                               needs to be checked
671
     * @param ResourceType &$resourceType            Resource type of the entry
672
     *                                               object
673
     * @param bool         &$needToSerializeResponse On return, this will contain
674
     *                                               True if response needs to be
675
     *                                               serialized, False otherwise
676
     * @param bool         $needToSerializeResponse
677
     *
678
     * @return string|null The ETag for the entry object if it has eTag properties
679
     *                     NULL otherwise
680
     */
681
    protected function compareETag(
682
        &$entryObject,
683
        ResourceType & $resourceType,
684
        &$needToSerializeResponse
685
    ) {
686
        $needToSerializeResponse = true;
687
        $eTag = null;
688
        $ifMatch = $this->getHost()->getRequestIfMatch();
689
        $ifNoneMatch = $this->getHost()->getRequestIfNoneMatch();
690
        if (is_null($entryObject)) {
691
            if (!is_null($ifMatch)) {
692
                throw ODataException::createPreConditionFailedError(
693
                    Messages::eTagNotAllowedForNonExistingResource()
694
                );
695
            }
696
697
            return;
698
        }
699
700
        if ($this->getConfiguration()->getValidateETagHeader() && !$resourceType->hasETagProperties()) {
701
            if (!is_null($ifMatch) || !is_null($ifNoneMatch)) {
702
                // No eTag properties but request has eTag headers, bad request
703
                throw ODataException::createBadRequestError(
704
                    Messages::noETagPropertiesForType()
705
                );
706
            }
707
708
            // We need write the response but no eTag header
709
            return;
710
        }
711
712
        if (!$this->getConfiguration()->getValidateETagHeader()) {
713
            // Configuration says do not validate ETag, so we will not write ETag header in the
714
            // response even though the requested resource support it
715
            return;
716
        }
717
718
        if (is_null($ifMatch) && is_null($ifNoneMatch)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
719
            // No request eTag header, we need to write the response
720
            // and eTag header
721
        } elseif (0 === strcmp($ifMatch, '*')) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
722
            // If-Match:* => we need to write the response and eTag header
723
        } elseif (0 === strcmp($ifNoneMatch, '*')) {
724
            // if-None-Match:* => Do not write the response (304 not modified),
725
            // but write eTag header
726
            $needToSerializeResponse = false;
727
        } else {
728
            $eTag = $this->getETagForEntry($entryObject, $resourceType);
729
            // Note: The following code for attaching the prefix W\"
730
            // and the suffix " can be done in getETagForEntry function
731
            // but that is causing an issue in Linux env where the
732
            // firefix browser is unable to parse the ETag in this case.
733
            // Need to follow up PHP core devs for this.
734
            $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
735
            if (!is_null($ifMatch)) {
736
                if (0 != strcmp($eTag, $ifMatch)) {
737
                    // Requested If-Match value does not match with current
738
                    // eTag Value then pre-condition error
739
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
740
                    throw ODataException::createPreConditionFailedError(
741
                        Messages::eTagValueDoesNotMatch()
742
                    );
743
                }
744
            } elseif (0 === strcmp($eTag, $ifNoneMatch)) {
745
                //304 not modified, but in write eTag header
746
                $needToSerializeResponse = false;
747
            }
748
        }
749
750
        if (is_null($eTag)) {
751
            $eTag = $this->getETagForEntry($entryObject, $resourceType);
752
            // Note: The following code for attaching the prefix W\"
753
            // and the suffix " can be done in getETagForEntry function
754
            // but that is causing an issue in Linux env where the
755
            // firefix browser is unable to parse the ETag in this case.
756
            // Need to follow up PHP core devs for this.
757
            $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
758
        }
759
760
        return $eTag;
761
    }
762
763
    /**
764
     * Returns the etag for the given resource.
765
     * Note: This function will not add W\" prefix and " suffix, its callers
766
     * repsonsibility.
767
     *
768
     * @param mixed        &$entryObject  Resource for which etag value needs to
769
     *                                    be returned
770
     * @param ResourceType &$resourceType Resource type of the $entryObject
771
     *
772
     * @return string|null ETag value for the given resource (with values encoded
773
     *                     for use in a URI) there are etag properties, NULL if
774
     *                     there is no etag property
775
     */
776
    protected function getETagForEntry(&$entryObject, ResourceType & $resourceType)
777
    {
778
        $eTag = null;
779
        $comma = null;
780
        foreach ($resourceType->getETagProperties() as $eTagProperty) {
781
            $type = $eTagProperty->getInstanceType();
782
            assert($type instanceof IType, '!$type instanceof IType');
783
784
            $value = null;
785
            $property = $eTagProperty->getName();
786
            try {
787
                //TODO #88...also this seems like dupe work
788
                $value = ReflectionHandler::getProperty($entryObject, $property);
789
            } catch (\ReflectionException $reflectionException) {
790
                throw ODataException::createInternalServerError(
791
                    Messages::failedToAccessProperty($property, $resourceType->getName())
792
                );
793
            }
794
795
            $eTagBase = $eTag . $comma;
796
            $eTag = $eTagBase . ((null == $value) ? 'null' : $type->convertToOData($value));
797
798
            $comma = ',';
799
        }
800
801 View Code Duplication
        if (!is_null($eTag)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
802
            // If eTag is made up of datetime or string properties then the above
803
            // IType::convertToOData will perform utf8 and url encode. But we don't
804
            // want this for eTag value.
805
            $eTag = urldecode(utf8_decode($eTag));
806
807
            return rtrim($eTag, ',');
808
        }
809
    }
810
811
    /**
812
     * This function will perform the following operations:
813
     * (1) Invoke delegateRequestProcessing method to process the request based
814
     *     on request method (GET, PUT/MERGE, POST, DELETE)
815
     * (3) If the result of processing of request needs to be serialized as HTTP
816
     *     response body (e.g. GET request result in single resource or resource
817
     *     collection, successful POST operation for an entity need inserted
818
     *     entity to be serialized back etc..), Serialize the result by using
819
     *     'serializeReultForResponseBody' method
820
     *     Set the serialized result to
821
     *     WebOperationContext::Current()::OutgoingWebResponseContext::Stream.
822
     */
823
    protected function handleRequest2()
824
    {
825
    }
826
827
    /**
828
     * This method will perform the following operations:
829
     * (1) If request method is GET, then result is already there in the
830
     *     RequestDescription so simply return the RequestDescription
831
     * (2). If request method is for CDU
832
     *      (Create/Delete/Update - POST/DELETE/PUT-MERGE) hand
833
     *      over the responsibility to respective handlers. The handler
834
     *      methods are:
835
     *      (a) handlePOSTOperation() => POST
836
     *      (b) handlePUTOperation() => PUT/MERGE
837
     *      (c) handleDELETEOperation() => DELETE
838
     * (3). Check whether its required to write any result to the response
839
     *      body
840
     *      (a). Request method is GET
841
     *      (b). Request is a POST for adding NEW Entry
842
     *      (c). Request is a POST for adding Media Resource Stream
843
     *      (d). Request is a POST for adding a link
844
     *      (e). Request is a DELETE for deleting entry or relationship
845
     *      (f). Request is a PUT/MERGE for updating an entry
846
     *      (g). Request is a PUT for updating a link
847
     *     In case a, b and c we need to write the result to response body,
848
     *     for d, e, f and g no body content.
849
     *
850
     * @return RequestDescription|null Instance of RequestDescription with
851
     *                                 result to be write back Null if no result to write
852
     */
853
    protected function delegateRequestProcessing()
854
    {
855
    }
856
857
    /**
858
     * Serialize the result in the current request description using
859
     * appropriate odata writer (AtomODataWriter/JSONODataWriter).
860
     */
861
    protected function serializeResultForResponseBody()
862
    {
863
    }
864
865
    /**
866
     * Handle POST request.
867
     *
868
     *
869
     * @throws NotImplementedException
870
     */
871
    protected function handlePOSTOperation()
872
    {
873
    }
874
875
    /**
876
     * Handle PUT/MERGE request.
877
     *
878
     *
879
     * @throws NotImplementedException
880
     */
881
    protected function handlePUTOperation()
882
    {
883
    }
884
885
    /**
886
     * Handle DELETE request.
887
     *
888
     *
889
     * @throws NotImplementedException
890
     */
891
    protected function handleDELETEOperation()
892
    {
893
    }
894
}
895