forked from openemr/openemr
-
Notifications
You must be signed in to change notification settings - Fork 1
/
RestControllerHelper.php
311 lines (279 loc) · 12.4 KB
/
RestControllerHelper.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
<?php
/**
* RestControllerHelper
*
* @package OpenEMR
* @link http://www.open-emr.org
* @author Matthew Vita <[email protected]>
* @copyright Copyright (c) 2018 Matthew Vita <[email protected]>
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/
namespace OpenEMR\RestControllers;
use OpenEMR\Common\Logging\SystemLogger;
use OpenEMR\Common\System\System;
use OpenEMR\FHIR\FhirSearchParameterType;
use OpenEMR\FHIR\R4\FHIRDomainResource\FHIROperationDefinition;
use OpenEMR\FHIR\R4\FHIRDomainResource\FHIRPatient;
use OpenEMR\FHIR\R4\FHIRElement\FHIRCanonical;
use OpenEMR\FHIR\R4\FHIRElement\FHIRCode;
use OpenEMR\FHIR\R4\FHIRElement\FHIRExtension;
use OpenEMR\FHIR\R4\FHIRElement\FHIRReference;
use OpenEMR\FHIR\R4\FHIRElement\FHIRRestfulCapabilityMode;
use OpenEMR\FHIR\R4\FHIRElement\FHIRString;
use OpenEMR\FHIR\R4\FHIRElement\FHIRTypeRestfulInteraction;
use OpenEMR\FHIR\R4\FHIRResource;
use OpenEMR\FHIR\R4\FHIRResource\FHIRCapabilityStatement\FHIRCapabilityStatementInteraction;
use OpenEMR\FHIR\R4\FHIRResource\FHIRCapabilityStatement\FHIRCapabilityStatementOperation;
use OpenEMR\FHIR\R4\FHIRResource\FHIRCapabilityStatement\FHIRCapabilityStatementResource;
use OpenEMR\FHIR\R4\FHIRResource\FHIRCapabilityStatement\FHIRCapabilityStatementRest;
use OpenEMR\Services\FHIR\IResourceUSCIGProfileService;
class RestControllerHelper
{
/**
* The resource endpoint names we want to skip over.
*/
const IGNORE_ENDPOINT_RESOURCES = ['.well-known', 'metadata'];
/**
* The default FHIR services class namespace
*/
const FHIR_SERVICES_NAMESPACE = "OpenEMR\\Services\\FHIR\\Fhir";
/**
* Configures the HTTP status code and payload returned within a response.
*
* @param $serviceResult
* @param $customRespPayload
* @param $idealStatusCode
* @return null
*/
public static function responseHandler($serviceResult, $customRespPayload, $idealStatusCode)
{
if ($serviceResult) {
http_response_code($idealStatusCode);
if ($customRespPayload) {
return $customRespPayload;
}
return $serviceResult;
}
// if no result is present return a 404 with a null response
http_response_code(404);
return null;
}
public static function validationHandler($validationResult)
{
if (property_exists($validationResult, 'isValid') && !$validationResult->isValid()) {
http_response_code(400);
$validationMessages = null;
if (property_exists($validationResult, 'getValidationMessages')) {
$validationMessages = $validationResult->getValidationMessages();
} else {
$validationMessages = $validationResult->getMessages();
}
return $validationMessages;
}
return null;
}
/**
* Parses a service processing result for standard Apis to determine the appropriate HTTP status code and response format
* for a request.
*
* The response body has a uniform structure with the following top level keys:
* - validationErrors
* - internalErrors
* - data
*
* The response data key conveys the data payload for a response. The payload is either a "top level" array for a
* single result, or an array for multiple results.
*
* @param $processingResult - The service processing result.
* @param $successStatusCode - The HTTP status code to return for a successful operation that completes without error.
* @param $isMultipleResultResponse - Indicates if the response contains multiple results.
* @return array[]
*/
public static function handleProcessingResult($processingResult, $successStatusCode, $isMultipleResultResponse = false): array
{
$httpResponseBody = [
"validationErrors" => [],
"internalErrors" => [],
"data" => []
];
if (!$processingResult->isValid()) {
http_response_code(400);
$httpResponseBody["validationErrors"] = $processingResult->getValidationMessages();
} elseif ($processingResult->hasInternalErrors()) {
http_response_code(500);
$httpResponseBody["internalErrors"] = $processingResult->getInternalErrors();
} else {
http_response_code($successStatusCode);
$dataResult = $processingResult->getData();
if (!$isMultipleResultResponse) {
$dataResult = (count($dataResult) === 0) ? [] : $dataResult[0];
}
$httpResponseBody["data"] = $dataResult;
}
return $httpResponseBody;
}
/**
* Parses a service processing result for FHIR endpoints to determine the appropriate HTTP status code and response format
* for a request.
*
* The response body has a normal Fhir Resource json:
*
* @param $processingResult - The service processing result.
* @param $successStatusCode - The HTTP status code to return for a successful operation that completes without error.
* @return array|mixed
*/
public static function handleFhirProcessingResult($processingResult, $successStatusCode)
{
$httpResponseBody = [];
if (!$processingResult->isValid()) {
http_response_code(400);
$httpResponseBody["validationErrors"] = $processingResult->getValidationMessages();
} elseif ($processingResult->hasInternalErrors()) {
http_response_code(500);
$httpResponseBody["internalErrors"] = $processingResult->getInternalErrors();
} else {
http_response_code($successStatusCode);
$dataResult = $processingResult->getData();
$httpResponseBody = $dataResult[0];
}
return $httpResponseBody;
}
public function setSearchParams($resource, FHIRCapabilityStatementResource $capResource, $service)
{
if (empty($service)) {
return; // nothing to do here as the service isn't defined.
}
$capResource->addSearchInclude('*');
foreach ($service->getSearchParams() as $fhirSearchField => $searchDefinition) {
$paramExists = false;
$type = $searchDefinition['type'] ?? FhirSearchParameterType::STRING;
foreach ($capResource->getSearchParam() as $searchParam) {
if (strcmp($searchParam->getName(), $fhirSearchField) == 0) {
$paramExists = true;
}
}
if (!$paramExists) {
$param = new FHIRResource\FHIRCapabilityStatement\FHIRCapabilityStatementSearchParam();
$param->setName($fhirSearchField);
$param->setType($type);
$capResource->addSearchParam($param);
}
}
}
/**
* Retrieves the fully qualified service class name for a given FHIR resource. It will only return a class that
* actually exists.
* @param $resource The name of the FHIR resource that we attempt to find the service class for.
* @param string $serviceClassNameSpace The namespace to find the class in. Defaults to self::FHIR_SERVICES_NAMESPACE
* @return string|null Returns the fully qualified name if the class is found, otherwise it returns null.
*/
public function getFullyQualifiedServiceClassForResource($resource, $serviceClassNameSpace = self::FHIR_SERVICES_NAMESPACE)
{
$serviceClass = $serviceClassNameSpace . $resource . "Service";
if (class_exists($serviceClass)) {
return $serviceClass;
}
return null;
}
public function addOperations($resource, $items, FHIRCapabilityStatementResource $capResource)
{
$operation = end($items);
// we want to skip over anything that's not a resource $operation
if ($operation === '$export') {
$operationName = strtolower($resource) . '-export';
// define export operation
$resource = new FHIRPatient();
$operation = new FHIRCapabilityStatementOperation();
$operation->setName($operationName);
$operation->setDefinition(new FHIRCanonical('http://hl7.org/fhir/uv/bulkdata/OperationDefinition/' . $operationName));
$extension = new FHIRExtension();
$extension->setValueCode(new FHIRCode('SHOULD'));
$extension->setUrl('http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation');
$operation->addExtension($extension);
$capResource->addOperation($operation);
}
}
public function addRequestMethods($items, FHIRCapabilityStatementResource $capResource)
{
$reqMethod = trim($items[0], " ");
$numberItems = count($items);
$code = "";
// we want to skip over $export operations.
if (end($items) === '$export') {
return;
}
// now setup our interaction types
if (strcmp($reqMethod, "GET") == 0) {
if (!empty(preg_match('/:/', $items[$numberItems - 1]))) {
$code = "read";
} else {
$code = "search-type";
}
} elseif (strcmp($reqMethod, "POST") == 0) {
$code = "insert";
} elseif (strcmp($reqMethod, "PUT") == 0) {
$code = "update";
}
if (!empty($code)) {
$interaction = new FHIRCapabilityStatementInteraction();
$restfulInteraction = new FHIRTypeRestfulInteraction();
$restfulInteraction->setValue($code);
$interaction->setCode($restfulInteraction);
$capResource->addInteraction($interaction);
}
}
public function getCapabilityRESTObject($routes, $serviceClassNameSpace = self::FHIR_SERVICES_NAMESPACE, $structureDefinition = "http://hl7.org/fhir/StructureDefinition/"): FHIRCapabilityStatementRest
{
$restItem = new FHIRCapabilityStatementRest();
$mode = new FHIRRestfulCapabilityMode();
$mode->setValue('server');
$restItem->setMode($mode);
$resourcesHash = array();
foreach ($routes as $key => $function) {
$items = explode("/", $key);
if ($serviceClassNameSpace == self::FHIR_SERVICES_NAMESPACE) {
// FHIR routes always have the resource at $items[2]
$resource = $items[2];
} else {
// API routes do not always have the resource at $items[2]
if (count($items) < 5) {
$resource = $items[2];
} elseif (count($items) < 7) {
$resource = $items[4];
if (substr($resource, 0, 1) === ':') {
// special behavior needed for the API portal route
$resource = $items[3];
}
} else { // count($items) < 9
$resource = $items[6];
}
}
if (!in_array($resource, self::IGNORE_ENDPOINT_RESOURCES)) {
$service = null;
$serviceClass = $this->getFullyQualifiedServiceClassForResource($resource, $serviceClassNameSpace);
if (!empty($serviceClass)) {
$service = new $serviceClass();
}
if (!array_key_exists($resource, $resourcesHash)) {
$capResource = new FHIRCapabilityStatementResource();
$capResource->setType(new FHIRCode($resource));
$capResource->setProfile(new FHIRCanonical($structureDefinition . $resource));
if ($service instanceof IResourceUSCIGProfileService) {
$profileUris = $service->getProfileURIs();
foreach ($profileUris as $uri) {
$capResource->addSupportedProfile(new FHIRCanonical($uri));
}
}
$resourcesHash[$resource] = $capResource;
}
$this->setSearchParams($resource, $resourcesHash[$resource], $service);
$this->addRequestMethods($items, $resourcesHash[$resource]);
$this->addOperations($resource, $items, $resourcesHash[$resource]);
}
}
foreach ($resourcesHash as $resource => $capResource) {
$restItem->addResource($capResource);
}
return $restItem;
}
}