use of org.ehrbase.response.openehr.CompositionResponseData in project ehrbase by ehrbase.
the class OpenehrCompositionController method updateComposition.
@PutMapping("/{ehr_id}/composition/{versioned_object_uid}")
// checkAbacPre /-Post attributes (type, subject, payload, content type)
@PreAuthorize("checkAbacPre(@openehrCompositionController.COMPOSITION, " + "@ehrService.getSubjectExtRef(#ehrIdString), #composition, #contentType)")
@Override
public ResponseEntity updateComposition(String openehrVersion, @RequestHeader(value = "openEHR-AUDIT_DETAILS", required = false) String openehrAuditDetails, @RequestHeader(value = CONTENT_TYPE, required = false) String contentType, @RequestHeader(value = ACCEPT, required = false) String accept, @RequestHeader(value = PREFER, required = false) String prefer, @RequestHeader(value = IF_MATCH) String ifMatch, @PathVariable(value = "ehr_id") String ehrIdString, @PathVariable(value = "versioned_object_uid") String versionedObjectUidString, @RequestBody String composition, HttpServletRequest request) {
UUID ehrId = getEhrUuid(ehrIdString);
UUID versionedObjectUid = getCompositionVersionedObjectUidString(versionedObjectUidString);
CompositionFormat compositionFormat = extractCompositionFormat(contentType);
// check if composition ID path variable is valid
compositionService.exists(versionedObjectUid);
// If the If-Match is not the latest latest existing version, throw error
if (!((versionedObjectUid + "::" + compositionService.getServerConfig().getNodename() + "::" + compositionService.getLastVersionNumber(extractVersionedObjectUidFromVersionUid(versionedObjectUid.toString()))).equals(ifMatch))) {
throw new PreconditionFailedException("If-Match header does not match latest existing version");
}
// If body already contains a composition uid it must match the {versioned_object_uid} in request url
Optional<String> inputUuid = Optional.ofNullable(compositionService.getUidFromInputComposition(composition, compositionFormat));
inputUuid.ifPresent(id -> {
// TODO it is further unclear what exactly the REST spec's "match" means, see: https://github.com/openEHR/specifications-ITS-REST/issues/83
if (!versionedObjectUid.equals(extractVersionedObjectUidFromVersionUid(id))) {
throw new PreconditionFailedException("UUID from input must match given versioned_object_uid in request URL");
}
});
// variable to overload with more specific object if requested
Optional<InternalResponse<CompositionResponseData>> respData = Optional.empty();
try {
Composition compoObj = compositionService.buildComposition(composition, compositionFormat, null);
// TODO should have EHR as parameter and check for existence as precondition - see EHR-245 (no direct EHR access in this controller)
// ifMatch header has to be tested for correctness already above
Optional<CompositionDto> dtoOptional = compositionService.update(ehrId, new ObjectVersionId(ifMatch), compoObj);
var compositionVersionUid = dtoOptional.orElseThrow(() -> new InternalServerException("Failed to create composition")).getComposition().getUid().toString();
var uri = URI.create(this.encodePath(getBaseEnvLinkURL() + "/rest/openehr/v1/ehr/" + ehrId.toString() + "/composition/" + compositionVersionUid));
List<String> headerList = Arrays.asList(LOCATION, ETAG, // whatever is required by REST spec - CONTENT_TYPE only needed for 200, so handled separately
LAST_MODIFIED);
UUID compositionId = extractVersionedObjectUidFromVersionUid(compositionVersionUid);
if (RETURN_REPRESENTATION.equals(prefer)) {
// both options extract needed info from versionUid
respData = buildCompositionResponseData(compositionId, extractVersionFromVersionUid(compositionVersionUid), accept, uri, headerList, () -> new CompositionResponseData(null, null));
} else {
// "minimal" is default fallback
respData = buildCompositionResponseData(compositionId, extractVersionFromVersionUid(compositionVersionUid), accept, uri, headerList, () -> null);
}
// Enriches request attributes with current compositionId for later audit processing
request.setAttribute(OpenEhrAuditInterceptor.EHR_ID_ATTRIBUTE, Collections.singleton(ehrId));
request.setAttribute(CompositionAuditInterceptor.COMPOSITION_ID_ATTRIBUTE, compositionId);
} catch (ObjectNotFoundException e) {
// composition not found
return ResponseEntity.notFound().build();
}
// returns 200 with body + headers, 204 only with headers or 500 error depending on what processing above yields
return respData.map(i -> Optional.ofNullable(i.getResponseData()).map(StructuredString::getValue).map(j -> ResponseEntity.ok().headers(i.getHeaders()).body(j)).orElse(ResponseEntity.noContent().headers(i.getHeaders()).build())).orElse(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
}
use of org.ehrbase.response.openehr.CompositionResponseData in project ehrbase by ehrbase.
the class OpenehrCompositionController method getCompositionByTime.
/**
* This mapping combines both GETs "/{ehr_id}/composition/{version_uid}" (via overlapping path)
* and "/{ehr_id}/composition/{versioned_object_uid}{?version_at_time}" (here). This is necessary
* because of the overlapping paths. Both mappings are specified to behave almost the same, so
* this solution works in this case.
*/
@GetMapping("/{ehr_id}/composition/{versioned_object_uid}{?version_at_time}")
// checkAbacPre /-Post attributes (type, subject, payload, content type)
@PostAuthorize("checkAbacPost(@openehrCompositionController.COMPOSITION, " + "@ehrService.getSubjectExtRef(#ehrIdString), returnObject, #accept)")
@Override
public ResponseEntity getCompositionByTime(@RequestHeader(value = ACCEPT, required = false) String accept, @PathVariable(value = "ehr_id") String ehrIdString, @PathVariable(value = "versioned_object_uid") String versionedObjectUid, @RequestParam(value = "version_at_time", required = false) String versionAtTime, HttpServletRequest request) {
UUID ehrId = getEhrUuid(ehrIdString);
// Note: Since this method can be called by another mapping as "almost overloaded" function some parameters might be semantically named wrong in that case. E.g. versionedObjectUid can contain a versionUid.
// Note: versionUid should be of format "uuid::domain::version", versionObjectUid of format "uuid"
UUID compositionUid = extractVersionedObjectUidFromVersionUid(// extracts UUID from long or short notation
versionedObjectUid);
if (compositionService.isDeleted(compositionUid)) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
// fallback 0 means latest version
int version = 0;
if (extractVersionFromVersionUid(versionedObjectUid) != 0) {
// the given ID contains a version, therefore this is case GET {version_uid}
version = extractVersionFromVersionUid(versionedObjectUid);
} else {
// case GET {versioned_object_uid}{?version_at_time}
Optional<OffsetDateTime> temporal = getVersionAtTimeParam();
if (versionAtTime != null && temporal.isPresent()) {
// when optional request parameter was provided, retrieve version according to given time
Optional<Integer> versionFromTimestamp = Optional.ofNullable(compositionService.getVersionByTimestamp(compositionUid, temporal.get().toLocalDateTime()));
version = versionFromTimestamp.orElseThrow(() -> new ObjectNotFoundException("composition", "No composition version matching the timestamp condition"));
}
// else continue with fallback: latest version
}
URI uri = URI.create(this.encodePath(getBaseEnvLinkURL() + "/rest/openehr/v1/ehr/" + ehrId.toString() + "/composition/" + versionedObjectUid));
List<String> headerList = Arrays.asList(LOCATION, ETAG, // whatever is required by REST spec - CONTENT_TYPE only needed for 200, so handled separately
LAST_MODIFIED);
Optional<InternalResponse<CompositionResponseData>> respData = buildCompositionResponseData(compositionUid, version, accept, uri, headerList, () -> new CompositionResponseData(null, null));
// Enriches request attributes with ehrId, compositionId and version for later audit processing
request.setAttribute(OpenEhrAuditInterceptor.EHR_ID_ATTRIBUTE, Collections.singleton(ehrId));
request.setAttribute(CompositionAuditInterceptor.COMPOSITION_ID_ATTRIBUTE, compositionUid);
request.setAttribute(CompositionAuditInterceptor.VERSION_ATTRIBUTE, version);
// returns 200 with body + headers, 204 only with headers or 500 error depending on what processing above yields
return respData.map(i -> Optional.ofNullable(i.getResponseData().getValue()).map(j -> ResponseEntity.ok().headers(i.getHeaders()).body(j)).orElse(ResponseEntity.noContent().headers(i.getHeaders()).build())).orElse(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
}
use of org.ehrbase.response.openehr.CompositionResponseData in project ehrbase by ehrbase.
the class OpenehrCompositionController method buildCompositionResponseData.
/**
* Builder method to prepare appropriate HTTP response. Flexible to either allow minimal or full
* representation of resource.
*
* @param <T> Type of the response body
* @param compositionId ID of the composition
* @param version 0 if latest, otherwise integer of specific version.
* @param accept Format the response should be delivered in, as given by request
* @param uri Location of resource
* @param headerList List of headers to be set for response
* @param factory Lambda function to constructor of desired object
* @return
*/
private <T extends CompositionResponseData> Optional<InternalResponse<T>> buildCompositionResponseData(UUID compositionId, Integer version, String accept, URI uri, List<String> headerList, Supplier<T> factory) {
// create either CompositionResponseData or null (means no body, only headers incl. link to resource), via lambda request
T minimalOrRepresentation = factory.get();
// do minimal scope steps
// create and supplement headers with data depending on which headers are requested
HttpHeaders respHeaders = new HttpHeaders();
for (String header : headerList) {
switch(header) {
case LOCATION:
respHeaders.setLocation(uri);
break;
case ETAG:
respHeaders.setETag("\"" + compositionId + "::" + compositionService.getServerConfig().getNodename() + "::" + compositionService.getLastVersionNumber(compositionId) + "\"");
break;
case LAST_MODIFIED:
// TODO should be VERSION.commit_audit.time_committed.value which is not implemented yet - mock for now
respHeaders.setLastModified(123124442);
break;
default:
}
}
// if (minimalOrRepresentation.getClass().equals(CompositionResponseData.class)) { // TODO make Optional.ofNull....
if (minimalOrRepresentation != null) {
// when this "if" is true the following casting can be executed and data manipulated by reference (handled by temporary variable)
CompositionResponseData objByReference = (CompositionResponseData) minimalOrRepresentation;
CompositionFormat format = extractCompositionFormat(accept);
// version handling allows to request specific version
Integer versionNumber = version;
if (versionNumber == 0) {
versionNumber = compositionService.getLastVersionNumber(compositionId);
}
Optional<CompositionDto> compositionDto = compositionService.retrieve(compositionId, versionNumber);
// TODO how to handle error situation here only with Optional? is there a better way without java 9 Optional.ifPresentOrElse()?
if (compositionDto.isPresent()) {
StructuredString ss = compositionService.serialize(compositionDto.get(), format);
objByReference.setValue(ss.getValue());
objByReference.setFormat(ss.getFormat());
// objByReference.setComposition(compositionService.serialize(compositionDto.get(), format));
} else {
// TODO undo creation of composition, if applicable
throw new ObjectNotFoundException("composition", "Couldn't retrieve composition");
}
// finally set last header
if (format.equals(CompositionFormat.XML)) {
respHeaders.setContentType(MediaType.APPLICATION_XML);
} else if (format.equals(CompositionFormat.FLAT) || format.equals(CompositionFormat.ECISFLAT) || format.equals(CompositionFormat.RAW)) {
respHeaders.setContentType(MediaType.APPLICATION_JSON);
}
}
return Optional.of(new InternalResponse<>(minimalOrRepresentation, respHeaders));
}
Aggregations