/
composition.go
544 lines (461 loc) · 20.9 KB
/
composition.go
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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
package gateway
import (
"context"
"encoding/json"
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/bsn-si/IPEHR-gateway/src/pkg/docs/model"
"github.com/bsn-si/IPEHR-gateway/src/pkg/docs/service"
"github.com/bsn-si/IPEHR-gateway/src/pkg/docs/service/composition"
"github.com/bsn-si/IPEHR-gateway/src/pkg/docs/service/processing"
proc "github.com/bsn-si/IPEHR-gateway/src/pkg/docs/service/processing"
"github.com/bsn-si/IPEHR-gateway/src/pkg/errors"
"github.com/bsn-si/IPEHR-gateway/src/pkg/helper"
)
type (
CompositionService interface {
helper.Finder
DefaultGroupAccess() *uuid.UUID
Create(ctx context.Context, userID, systemID string, ehrUUID, groupAccessUUID *uuid.UUID, composition *model.Composition, procRequest *proc.Request) (*model.Composition, error)
Update(ctx context.Context, procRequest *proc.Request, userID, systemID string, ehrUUID, groupAccessUUID *uuid.UUID, composition *model.Composition) (*model.Composition, error)
GetLastByBaseID(ctx context.Context, userID, systemID string, ehrUUID *uuid.UUID, versionUID string) (*model.Composition, error)
GetByID(ctx context.Context, userID, systemID string, ehrUUID *uuid.UUID, versionUID string) (*model.Composition, error)
DeleteByID(ctx context.Context, procRequest *proc.Request, ehrUUID *uuid.UUID, versionUID, userID, systemID string) (string, error)
GetList(ctx context.Context, userID, systemID string) ([]*model.EhrDocumentItem, error)
}
Indexer interface {
GetEhrUUIDByUserID(ctx context.Context, userID, systemID string) (*uuid.UUID, error)
}
ProcessingService interface {
NewRequest(reqID, userID, ehrUUID string, kind processing.RequestKind) (*processing.Request, error)
}
CompositionHandler struct {
service CompositionService
indexer Indexer
processingSvc ProcessingService
baseURL string
}
)
func NewCompositionHandler(docService *service.DefaultDocumentService, compositionService *composition.Service, baseURL string) *CompositionHandler {
return &CompositionHandler{
service: compositionService,
indexer: docService.Infra.Index,
processingSvc: docService.Proc,
baseURL: baseURL,
}
}
// Create
//
// @Summary Create COMPOSITION
// @Description Work in progress...
// @Description Creates the first version of a new COMPOSITION in the EHR identified by ehr_id.
// @Description
// @Tags COMPOSITION
// @Accept json
// @Produce json
// @Param ehr_id path string true "EHR identifier. Example: 7d44b88c-4199-4bad-97dc-d78268e01398"
// @Param Authorization header string true "Bearer AccessToken"
// @Param AuthUserId header string true "UserId"
// @Param EhrSystemId header string false "The identifier of the system, typically a reverse domain identifier"
// @Param GroupAccessId header string false "GroupAccessId - UUID. If not specified, the default access group will be used."
// @Param Prefer header string true "The new EHR resource is returned in the body when the request’s `Prefer` header value is `return=representation`, otherwise only headers are returned."
// @Param Request body model.Composition true "COMPOSITION"
// @Success 201 {object} model.Composition
// @Header 201 {string} Location "{baseUrl}/ehr/7d44b88c-4199-4bad-97dc-d78268e01398/composition/8849182c-82ad-4088-a07f-48ead4180515::openEHRSys.example.com::1"
// @Header 201 {string} ETag "8849182c-82ad-4088-a07f-48ead4180515::openEHRSys.example.com::1"
// @Header 201 {string} RequestID "Request identifier"
// @Failure 400 "Is returned when the request has invalid ehr_id or invalid content (e.g. content could not be converted to a valid COMPOSITION object)"
// @Failure 404 "Is returned when an EHR with ehr_id does not exist."
// @Failure 422 "Is returned when the content could be converted to a COMPOSITION, but there are semantic validation errors, such as the underlying template is not known or is not validating the supplied COMPOSITION)."
// @Failure 500 "Is returned when an unexpected error occurs while processing a request"
// @Router /ehr/{ehr_id}/composition [post]
func (h *CompositionHandler) Create(c *gin.Context) {
systemID := c.GetString("ehrSystemID")
reqID := c.GetString("reqID")
ehrID := c.Param("ehrid")
ehrUUID, err := uuid.Parse(ehrID)
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
userID := c.GetString("userID")
if userID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "userID is empty"})
return
}
userEhrUUID, err := h.indexer.GetEhrUUIDByUserID(c, userID, systemID)
switch {
case err != nil && errors.Is(err, errors.ErrIsNotExist):
c.AbortWithStatus(http.StatusNotFound)
return
case err != nil:
log.Println("GetEhrIDByUser error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
if userEhrUUID.String() != ehrUUID.String() {
log.Printf("userEhrUUID and ehrUUID is not equal: %s != %s", userEhrUUID, ehrUUID)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
groupAccessUUID := h.service.DefaultGroupAccess()
if c.GetHeader("GroupAccessId") != "" {
UUID, err := uuid.Parse(c.GetHeader("GroupAccessId"))
if err != nil {
log.Println(err)
c.JSON(http.StatusBadRequest, gin.H{"error": "GroupAccessId parsing error"})
return
}
groupAccessUUID = &UUID
}
composition := &model.Composition{}
if err := json.NewDecoder(c.Request.Body).Decode(composition); err != nil {
log.Println("Composition Create request unmarshal error", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Request body parsing error"})
return
}
defer c.Request.Body.Close()
if ok, _ := composition.Validate(); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "Request validation error"})
return
}
procRequest, err := h.processingSvc.NewRequest(reqID, userID, ehrUUID.String(), proc.RequestCompositionCreate)
if err != nil {
log.Println("Composition create NewRequest error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
// Composition document creating
doc, err := h.service.Create(c, userID, systemID, &ehrUUID, groupAccessUUID, composition, procRequest)
if err != nil {
log.Println("Composition creating error:", err)
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Composition creating error"})
return
}
if err := procRequest.Commit(); err != nil {
log.Println("Composition procRequest commit error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
h.respondWithDocOrHeaders(ehrID, doc, c)
}
// GetByID
//
// @Summary Get COMPOSITION by version id
// @Description Retrieves a particular version of the COMPOSITION identified by `version_uid` and associated with the EHR identified by `ehr_id`.
// @Description
// @Tags COMPOSITION
// @Accept json
// @Produce json
// @Param ehr_id path string true "EHR identifier taken from EHR.ehr_id.value. Example: 7d44b88c-4199-4bad-97dc-d78268e01398"
// @Param version_uid path string true "VERSION identifier taken from VERSION.uid.value. Example: 8849182c-82ad-4088-a07f-48ead4180515::openEHRSys.example.com::1"
// @Param Authorization header string true "Bearer AccessToken"
// @Param AuthUserId header string true "UserId"
// @Param EhrSystemId header string false "The identifier of the system, typically a reverse domain identifier"
// @Success 200 {object} model.Composition
// @Success 202 "Is returned when the request is still being processed"
// @Failure 204 "Is returned when the COMPOSITION is deleted (logically)."
// @Failure 400 "Is returned when AuthUserId is not specified"
// @Failure 404 "is returned when an EHR with `ehr_id` does not exist or when an COMPOSITION with `version_uid` does not exist."
// @Failure 500 "Is returned when an unexpected error occurs while processing a request"
// @Router /ehr/{ehr_id}/composition/{version_uid} [get]
func (h *CompositionHandler) GetByID(c *gin.Context) {
ehrID := c.Param("ehrid")
systemID := c.GetString("ehrSystemID")
ehrUUID, err := uuid.Parse(ehrID)
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
versionUID := c.Param("version_uid")
//TODO validate versionUID
userID := c.GetString("userID")
if userID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "userID is empty"})
return
}
// Checking EHR does not exist
userEhrUUID, err := h.indexer.GetEhrUUIDByUserID(c, userID, systemID)
switch {
case err != nil && errors.Is(err, errors.ErrIsNotExist):
c.AbortWithStatus(http.StatusNotFound)
return
case err != nil:
log.Println("GetEhrIDByUser error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
if userEhrUUID.String() != ehrUUID.String() {
log.Printf("userEhrUUID and ehrUUID is not equal: %s != %s", userEhrUUID, ehrUUID)
c.AbortWithStatus(http.StatusInternalServerError)
}
data, err := h.service.GetByID(c, userID, systemID, &ehrUUID, versionUID)
if err != nil {
if errors.Is(err, errors.ErrNotFound) {
c.AbortWithStatus(http.StatusNotFound)
} else if errors.Is(err, errors.ErrAlreadyDeleted) {
c.AbortWithStatus(http.StatusNoContent)
} else if errors.Is(err, errors.ErrIsInProcessing) {
c.AbortWithStatus(http.StatusAccepted)
} else {
log.Println(err)
c.AbortWithStatus(http.StatusInternalServerError)
}
return
}
c.JSON(http.StatusOK, data)
}
// Delete
//
// @Summary Deletes the COMPOSITION by version id
// @Description Deletes the COMPOSITION identified by `preceding_version_uid` and associated with the EHR identified by `ehr_id`.
// @Description
// @Tags COMPOSITION
// @Accept json
// @Produce json
// @Param ehr_id path string true "EHR identifier taken from EHR.ehr_id.value. Example: 7d44b88c-4199-4bad-97dc-d78268e01398"
// @Param preceding_version_uid path string true "Identifier of the COMPOSITION to be deleted. This MUST be the last (most recent) version. Example: `8849182c-82ad-4088-a07f-48ead4180515::openEHRSys.example.com::1`"
// @Param Authorization header string true "Bearer AccessToken"
// @Param AuthUserId header string true "UserId"
// @Param EhrSystemId header string false "The identifier of the system, typically a reverse domain identifier"
// @Failure 204 "`No Content` is returned when COMPOSITION was deleted."
// @Header 204 {string} RequestID "Request identifier"
// @Failure 400 "`Bad Request` is returned when the composition with `preceding_version_uid` is already deleted."
// @Failure 404 "`Not Found` is returned when an EHR with ehr_id does not exist or when a COMPOSITION with preceding_version_uid does not exist."
// @Failure 409 "`Conflict` is returned when supplied `preceding_version_uid` doesn’t match the latest version. Returns latest version in the Location and ETag headers."
// @Failure 500 "Is returned when an unexpected error occurs while processing a request"
// @Router /ehr/{ehr_id}/composition/{preceding_version_uid} [delete]
func (h *CompositionHandler) Delete(c *gin.Context) {
ehrID := c.Param("ehrid")
systemID := c.GetString("ehrSystemID")
//TODO validate ehrID
ehrUUID, err := uuid.Parse(ehrID)
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
versionUID := c.Param("preceding_version_uid")
//TODO validate versionUID
userID := c.GetString("userID")
if userID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "userID is empty"})
return
}
userEhrUUID, err := h.indexer.GetEhrUUIDByUserID(c, userID, systemID)
if err != nil {
if errors.Is(err, errors.ErrIsNotExist) {
c.AbortWithStatus(http.StatusNotFound)
} else {
c.AbortWithStatus(http.StatusInternalServerError)
}
return
}
if userEhrUUID.String() != ehrUUID.String() {
log.Printf("userEhrUUID and ehrUUID is not equal: %s != %s", userEhrUUID, ehrUUID)
c.AbortWithStatus(http.StatusInternalServerError)
}
reqID := c.GetString("reqID")
procRequest, err := h.processingSvc.NewRequest(reqID, userID, ehrUUID.String(), proc.RequestCompositionDelete)
if err != nil {
log.Println("Composition delete NewRequest error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
newUID, err := h.service.DeleteByID(c, procRequest, &ehrUUID, versionUID, userID, systemID)
if err != nil {
if errors.Is(err, errors.ErrNotFound) {
c.AbortWithStatus(http.StatusNotFound)
} else if errors.Is(err, errors.ErrAlreadyDeleted) {
c.AbortWithStatus(http.StatusBadRequest)
} else {
log.Println("DeleteByID error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
}
return
}
if err := procRequest.Commit(); err != nil {
log.Println("Composition delete procRequest commit error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
h.addResponseHeaders(ehrID, newUID, c)
c.AbortWithStatus(http.StatusNoContent)
}
// Update
//
// @Summary Updates the COMPOSITION by version id
// @Description Updates COMPOSITION identified by `versioned_object_uid` and associated with the EHR
// @Description identified by `ehr_id`. If the request body already contains a COMPOSITION.uid.value,
// @Description it must match the `versioned_object_uid` in the URL. The existing latest `version_uid`
// @Description of COMPOSITION resource (i.e the `preceding_version_uid`) must be specified in the `If-Match` header.
// @Description
// @Tags COMPOSITION
// @Accept json
// @Produce json
// @Param ehr_id path string true "EHR identifier taken from EHR.ehr_id.value. Example: 7d44b88c-4199-4bad-97dc-d78268e01398"
// @Param versioned_object_uid path string true "identifier of the COMPOSITION to be updated. Example: `8849182c-82ad-4088-a07f-48ead4180515`"
// @Param Authorization header string true "Bearer AccessToken"
// @Param AuthUserId header string true "UserId"
// @Param EhrSystemId header string false "The identifier of the system, typically a reverse domain identifier"
// @Param Prefer header string true "The updated COMPOSITION resource is returned to the body when the request’s `Prefer` header value is `return=representation`, otherwise only headers are returned."
// @Param If-Match header string true "The existing latest version_uid of COMPOSITION resource (i.e the preceding_version_uid). Example: `8849182c-82ad-4088-a07f-48ead4180515::openEHRSys.example.com::1`"
// @Param Request body model.Composition true "List of changes in COMPOSITION"
// @Success 200 {object} model.Composition "Is returned when the COMPOSITION is successfully updated and the updated resource is returned in the body when Prefer header value is `return=representation.`"
// @Header 200 {string} Location "{baseUrl}/ehr/7d44b88c-4199-4bad-97dc-d78268e01398/composition/8849182c-82ad-4088-a07f-48ead4180515::openEHRSys.example.com::2"
// @Header 200 {string} ETag "8849182c-82ad-4088-a07f-48ead4180515::openEHRSys.example.com::2"
// @Header 200 {string} RequestID "Request identifier"
// @Failure 422 "`Unprocessable Entity` is returned when the content could be converted to a COMPOSITION, but there are semantic validation errors, such as the underlying template is not known or is not validating the supplied COMPOSITION)."
// @Failure 400 "`Bad Request` is returned when the request has invalid `ehr_id` or invalid content (e.g. either the body of the request could not be read, or converted to a valid COMPOSITION object)"
// @Failure 404 "`Not Found` is returned when an EHR with ehr_id does not exist or when a COMPOSITION with version_object_uid does not exist."
// @Failure 412 "`Version conflict` is returned when `If-Match` request header doesn’t match the latest version (of this versioned object) on the service side. Returns also latest `version_uid` in the `Location` and `ETag` headers."
// @Failure 500 "Is returned when an unexpected error occurs while processing a request"
// @Router /ehr/{ehr_id}/composition/{versioned_object_uid} [put]
func (h CompositionHandler) Update(c *gin.Context) {
systemID := c.GetString("ehrSystemID")
reqID := c.GetString("reqID")
ehrID := c.Param("ehrid")
ehrUUID, err := uuid.Parse(ehrID)
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
versionUID := c.Param("versioned_object_uid")
//TODO validate versionUID
precedingVersionUID := c.GetHeader("If-Match")
if precedingVersionUID == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "If-Match is empty"})
return
}
userID := c.GetString("userID")
if userID == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "userID is empty"})
return
}
userEhrUUID, err := h.indexer.GetEhrUUIDByUserID(c, userID, systemID)
switch {
case err != nil && errors.Is(err, errors.ErrIsNotExist):
c.AbortWithStatus(http.StatusNotFound)
return
case err != nil:
log.Println("GetEhrIDByUser error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
if userEhrUUID.String() != ehrUUID.String() {
log.Printf("userEhrUUID and ehrUUID is not equal: %s != %s", userEhrUUID, ehrUUID)
c.AbortWithStatus(http.StatusInternalServerError)
}
groupAccessUUID := h.service.DefaultGroupAccess()
if c.GetHeader("GroupAccessId") != "" {
UUID, err := uuid.Parse(c.GetHeader("GroupAccessId"))
if err != nil {
log.Println(err)
c.JSON(http.StatusBadRequest, gin.H{"error": "GroupAccessId parsing error"})
return
}
groupAccessUUID = &UUID
}
compositionUpdate := model.Composition{}
if err := json.NewDecoder(c.Request.Body).Decode(&compositionUpdate); err != nil {
log.Println("Composition Update request unmarshal error", err)
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Request body parsing error"})
return
}
defer c.Request.Body.Close()
if ok, _ := compositionUpdate.Validate(); !ok {
c.AbortWithStatus(http.StatusUnprocessableEntity)
return
}
if compositionUpdate.UID.Value != precedingVersionUID {
c.AbortWithStatus(http.StatusUnprocessableEntity)
return
}
compositionLast, err := h.service.GetLastByBaseID(c, userID, systemID, &ehrUUID, versionUID)
if err != nil {
if errors.Is(err, errors.ErrIsNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
} else if errors.Is(err, errors.ErrIsInProcessing) {
c.AbortWithStatus(http.StatusAccepted)
return
}
log.Println("GetLastByBaseID error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
if compositionLast.UID.Value != precedingVersionUID {
h.addResponseHeaders(ehrID, compositionLast.UID.Value, c)
c.AbortWithStatus(http.StatusPreconditionFailed)
return
}
procRequest, err := h.processingSvc.NewRequest(reqID, userID, ehrUUID.String(), proc.RequestCompositionUpdate)
if err != nil {
log.Println("Compocition update NewRequest error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
compositionUpdated, err := h.service.Update(c, procRequest, userID, systemID, &ehrUUID, groupAccessUUID, &compositionUpdate)
if err != nil {
log.Println("Composition Update error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
if err := procRequest.Commit(); err != nil {
log.Println("Composition update procRequest commit error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
h.addResponseHeaders(ehrID, compositionUpdated.UID.Value, c)
c.JSON(http.StatusOK, compositionUpdated)
}
// List
//
// @Summary Get all COMPOSITIONs
// @Description Retrieves all versions of all COMPOSITIONs associated with the EHR identified by `ehr_id`.
// @Description
// @Tags COMPOSITION
// @Accept json
// @Produce json
// @Param ehr_id path string true "EHR identifier taken from EHR.ehr_id.value. Example: 7d44b88c-4199-4bad-97dc-d78268e01398"
// @Param Authorization header string true "Bearer AccessToken"
// @Param AuthUserId header string true "UserId"
// @Param EhrSystemId header string false "The identifier of the system, typically a reverse domain identifier"
// @Success 200 {object} []model.EhrDocumentItem
// @Failure 400 "Is returned when AuthUserId or EhrSystemId is not specified"
// @Failure 404 "is returned when an EHR with `ehr_id` does not exist."
// @Failure 500 "Is returned when an unexpected error occurs while processing a request"
// @Router /ehr/{ehr_id}/composition [get]
func (h CompositionHandler) GetList(c *gin.Context) {
systemID := c.GetString("ehrSystemID")
userID := c.GetString("userID")
list, err := h.service.GetList(c, userID, systemID)
if err != nil {
if errors.Is(err, errors.ErrNotFound) {
c.AbortWithStatus(http.StatusNotFound)
}
log.Println("Composition GetList error:", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
if list == nil {
list = []*model.EhrDocumentItem{}
}
c.JSON(http.StatusOK, list)
}
func (h *CompositionHandler) respondWithDocOrHeaders(ehrID string, doc *model.Composition, c *gin.Context) {
uid := doc.UID.Value
h.addResponseHeaders(ehrID, uid, c)
prefer := c.Request.Header.Get("Prefer")
//nolint:goconst
if prefer == "return=representation" {
c.JSON(http.StatusCreated, doc)
} else {
c.AbortWithStatus(http.StatusCreated)
}
}
func (h *CompositionHandler) addResponseHeaders(ehrID string, uid string, c *gin.Context) {
c.Header("Location", h.baseURL+"/v1/ehr/"+ehrID+"/composition/"+uid)
c.Header("ETag", uid)
}