Search in sources :

Example 1 with CompositionFormat

use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.

the class CustomMethodSecurityExpressionRoot method templateHandling.

/**
 * Handles template ID extraction of specific payload.
 * <p>
 * Payload will be a response body string, in case of @PostAuthorize.
 * <p>
 * Payload will be request body string, or already deserialized object (e.g. EhrStatus), in case of @PreAuthorize.
 * @param type Object type of scope
 * @param payload Payload object, either request's input or response's output
 * @param contentType Content type from the scope
 * @param requestMap ABAC request attribute map to add the result
 * @param authType Pre- or PostAuthorize, determines payload style (string or object)
 */
private void templateHandling(String type, Object payload, String contentType, Map<String, Object> requestMap, String authType) {
    switch(type) {
        case BaseController.EHR:
            throw new IllegalArgumentException("ABAC: Unsupported configuration: Can't set template ID for EHR type.");
        case BaseController.EHR_STATUS:
            throw new IllegalArgumentException("ABAC: Unsupported configuration: Can't set template ID for EHR_STATUS type.");
        case BaseController.COMPOSITION:
            String content = "";
            if (authType.equals(POST)) {
                // @PostAuthorize gives a ResponseEntity type for "returnObject", so payload is of that type
                if (((ResponseEntity) payload).hasBody()) {
                    Object body = ((ResponseEntity) payload).getBody();
                    // can have "No content" here (even with some data in the body) if the compo was (logically) deleted
                    if (((ResponseEntity<?>) payload).getStatusCode().equals(HttpStatus.NO_CONTENT)) {
                        if (body instanceof Map) {
                            Object error = ((Map<?, ?>) body).get("error");
                            if (error != null) {
                                if (((String) error).contains("delet")) {
                                    // composition was deleted, so nothing to check here, skip
                                    break;
                                }
                            }
                        }
                        throw new InternalServerException("ABAC: Unexpected empty response from composition reuquest");
                    }
                    if (body instanceof OriginalVersionResponseData) {
                        // case of versioned_composition --> fast path, because template is easy to get
                        if (((OriginalVersionResponseData<?>) body).getData() instanceof Composition) {
                            String template = Objects.requireNonNull(((Composition) ((OriginalVersionResponseData<?>) body).getData()).getArchetypeDetails().getTemplateId()).getValue();
                            requestMap.put(TEMPLATE, template);
                            // special case, so done here, exit
                            break;
                        }
                    } else if (body instanceof String) {
                        content = (String) body;
                    } else {
                        throw new InternalServerException("ABAC: unexpected composition payload object");
                    }
                } else {
                    throw new InternalServerException("ABAC: unexpected empty response body");
                }
            } else if (authType.equals(PRE)) {
                try {
                    // try if this is the Delete composition case. Payload would contain the UUID of the compo.
                    ObjectVersionId versionId = new ObjectVersionId((String) payload);
                    UUID compositionUid = UUID.fromString(versionId.getRoot().getValue());
                    Optional<CompositionDto> compoDto = compositionService.retrieve(compositionUid, null);
                    if (compoDto.isPresent()) {
                        Composition c = compoDto.get().getComposition();
                        requestMap.put(TEMPLATE, c.getArchetypeDetails().getTemplateId().getValue());
                        // special case, so done here, exit
                        break;
                    } else {
                        throw new InternalServerException("ABAC: unexpected empty response from composition delete");
                    }
                } catch (IllegalArgumentException e) {
                    // if not an UUID, the payload is a composition itself so continue
                    content = (String) payload;
                }
            } else {
                throw new InternalServerException("ABAC: invalid auth type given.");
            }
            String templateId;
            if (MediaType.parseMediaType(contentType).isCompatibleWith(MediaType.APPLICATION_JSON)) {
                templateId = compositionService.getTemplateIdFromInputComposition(content, CompositionFormat.JSON);
            } else if (MediaType.parseMediaType(contentType).isCompatibleWith(MediaType.APPLICATION_XML)) {
                templateId = compositionService.getTemplateIdFromInputComposition(content, CompositionFormat.XML);
            } else {
                throw new IllegalArgumentException("ABAC: Only JSON and XML composition are supported.");
            }
            requestMap.put(TEMPLATE, templateId);
            break;
        case BaseController.CONTRIBUTION:
            CompositionFormat format;
            if (MediaType.parseMediaType(contentType).isCompatibleWith(MediaType.APPLICATION_JSON)) {
                format = CompositionFormat.JSON;
            } else if (MediaType.parseMediaType(contentType).isCompatibleWith(MediaType.APPLICATION_XML)) {
                format = CompositionFormat.XML;
            } else {
                throw new IllegalArgumentException("ABAC: Only JSON and XML composition are supported.");
            }
            if (payload instanceof String) {
                Set<String> templates = contributionService.getListOfTemplates((String) payload, format);
                requestMap.put(TEMPLATE, templates);
                break;
            } else {
                throw new InternalServerException("ABAC: invalid POST contribution payload.");
            }
        case BaseController.QUERY:
            // special case of type QUERY, where multiple subjects are possible
            if (payload instanceof Map) {
                if (((Map<?, ?>) payload).containsKey(AuditVariables.TEMPLATE_PATH)) {
                    Set<String> templates = (Set) ((Map<?, ?>) payload).get(AuditVariables.TEMPLATE_PATH);
                    Set<String> templateSet = new HashSet<>(templates);
                    // put result set into the requestMap and exit
                    requestMap.put(TEMPLATE, templateSet);
                    break;
                } else {
                    throw new InternalServerException("ABAC: AQL audit template data unavailable.");
                }
            } else {
                throw new InternalServerException("ABAC: AQL audit template data malformed.");
            }
        default:
            throw new InternalServerException("ABAC: Invalid type given from Pre- or PostAuthorize");
    }
}
Also used : CompositionFormat(org.ehrbase.response.ehrscape.CompositionFormat) Composition(com.nedap.archie.rm.composition.Composition) HashSet(java.util.HashSet) Set(java.util.Set) Optional(java.util.Optional) InternalServerException(org.ehrbase.api.exception.InternalServerException) ObjectVersionId(com.nedap.archie.rm.support.identification.ObjectVersionId) OriginalVersionResponseData(org.ehrbase.response.openehr.OriginalVersionResponseData) ResponseEntity(org.springframework.http.ResponseEntity) UUID(java.util.UUID) HashMap(java.util.HashMap) Map(java.util.Map) HashSet(java.util.HashSet)

Example 2 with CompositionFormat

use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.

the class OpenehrContributionController method buildContributionResponseData.

private <T extends ContributionResponseData> Optional<InternalResponse<T>> buildContributionResponseData(UUID contributionId, UUID ehrId, 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("\"" + contributionId + "\"");
                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 response data objects was created as "representation" do all task from wider scope, too
    if (minimalOrRepresentation != null) {
        // when this "if" is true the following casting can be executed and data manipulated by reference (handled by temporary variable)
        ContributionResponseData objByReference = minimalOrRepresentation;
        // retrieve contribution
        Optional<ContributionDto> contribution = contributionService.getContribution(ehrId, contributionId);
        // set all response field according to retrieved contribution
        objByReference.setUid(new HierObjectId(contributionId.toString()));
        List<ObjectRef<ObjectVersionId>> refs = new LinkedList<>();
        contribution.get().getObjectReferences().forEach((id, type) -> refs.add(new ObjectRef<>(new ObjectVersionId(id), "local", type)));
        objByReference.setVersions(refs);
        objByReference.setAudit(contribution.get().getAuditDetails());
        CompositionFormat format = extractCompositionFormat(accept);
        // finally set last header
        if (format.equals(CompositionFormat.XML)) {
            respHeaders.setContentType(MediaType.APPLICATION_XML);
        } else if (format.equals(CompositionFormat.JSON) || format.equals(CompositionFormat.FLAT) || format.equals(CompositionFormat.ECISFLAT) || format.equals(CompositionFormat.RAW)) {
            respHeaders.setContentType(MediaType.APPLICATION_JSON);
        } else {
            throw new NotAcceptableException("Wrong Accept header in request");
        }
    }
    return Optional.of(new InternalResponse<>(minimalOrRepresentation, respHeaders));
}
Also used : HttpHeaders(org.springframework.http.HttpHeaders) CompositionFormat(org.ehrbase.response.ehrscape.CompositionFormat) ContributionResponseData(org.ehrbase.response.openehr.ContributionResponseData) ContributionDto(org.ehrbase.response.ehrscape.ContributionDto) ObjectVersionId(com.nedap.archie.rm.support.identification.ObjectVersionId) LinkedList(java.util.LinkedList) NotAcceptableException(org.ehrbase.api.exception.NotAcceptableException) ObjectRef(com.nedap.archie.rm.support.identification.ObjectRef) HierObjectId(com.nedap.archie.rm.support.identification.HierObjectId)

Example 3 with CompositionFormat

use of org.ehrbase.response.ehrscape.CompositionFormat 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());
}
Also used : CompositionResponseData(org.ehrbase.response.openehr.CompositionResponseData) PathVariable(org.springframework.web.bind.annotation.PathVariable) Arrays(java.util.Arrays) RequestParam(org.springframework.web.bind.annotation.RequestParam) Composition(com.nedap.archie.rm.composition.Composition) PreAuthorize(org.springframework.security.access.prepost.PreAuthorize) ZonedDateTime(java.time.ZonedDateTime) LocalDateTime(java.time.LocalDateTime) ObjectNotFoundException(org.ehrbase.api.exception.ObjectNotFoundException) Autowired(org.springframework.beans.factory.annotation.Autowired) RequestMapping(org.springframework.web.bind.annotation.RequestMapping) Supplier(java.util.function.Supplier) RequestBody(org.springframework.web.bind.annotation.RequestBody) HttpServletRequest(javax.servlet.http.HttpServletRequest) PutMapping(org.springframework.web.bind.annotation.PutMapping) CompositionResponseData(org.ehrbase.response.openehr.CompositionResponseData) CompositionAuditInterceptor(org.ehrbase.rest.openehr.audit.CompositionAuditInterceptor) GetMapping(org.springframework.web.bind.annotation.GetMapping) URI(java.net.URI) PreconditionFailedException(org.ehrbase.api.exception.PreconditionFailedException) ObjectVersionId(com.nedap.archie.rm.support.identification.ObjectVersionId) ResponseStatus(org.springframework.web.bind.annotation.ResponseStatus) DeleteMapping(org.springframework.web.bind.annotation.DeleteMapping) PostMapping(org.springframework.web.bind.annotation.PostMapping) HttpHeaders(org.springframework.http.HttpHeaders) MediaType(org.springframework.http.MediaType) CompositionDto(org.ehrbase.response.ehrscape.CompositionDto) PostAuthorize(org.springframework.security.access.prepost.PostAuthorize) UUID(java.util.UUID) RestController(org.springframework.web.bind.annotation.RestController) ZoneId(java.time.ZoneId) StructuredString(org.ehrbase.response.ehrscape.StructuredString) BaseController(org.ehrbase.rest.BaseController) Objects(java.util.Objects) HttpStatus(org.springframework.http.HttpStatus) List(java.util.List) OffsetDateTime(java.time.OffsetDateTime) CompositionService(org.ehrbase.api.service.CompositionService) InternalServerException(org.ehrbase.api.exception.InternalServerException) OpenEhrAuditInterceptor(org.ehrbase.rest.openehr.audit.OpenEhrAuditInterceptor) InternalResponse(org.ehrbase.rest.util.InternalResponse) Optional(java.util.Optional) CompositionApiSpecification(org.ehrbase.rest.openehr.specification.CompositionApiSpecification) ResponseEntity(org.springframework.http.ResponseEntity) RequestHeader(org.springframework.web.bind.annotation.RequestHeader) Collections(java.util.Collections) CompositionFormat(org.ehrbase.response.ehrscape.CompositionFormat) CompositionFormat(org.ehrbase.response.ehrscape.CompositionFormat) Composition(com.nedap.archie.rm.composition.Composition) InternalServerException(org.ehrbase.api.exception.InternalServerException) StructuredString(org.ehrbase.response.ehrscape.StructuredString) InternalResponse(org.ehrbase.rest.util.InternalResponse) ObjectVersionId(com.nedap.archie.rm.support.identification.ObjectVersionId) ObjectNotFoundException(org.ehrbase.api.exception.ObjectNotFoundException) CompositionDto(org.ehrbase.response.ehrscape.CompositionDto) StructuredString(org.ehrbase.response.ehrscape.StructuredString) PreconditionFailedException(org.ehrbase.api.exception.PreconditionFailedException) UUID(java.util.UUID) PutMapping(org.springframework.web.bind.annotation.PutMapping) PreAuthorize(org.springframework.security.access.prepost.PreAuthorize)

Example 4 with CompositionFormat

use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.

the class EhrController method buildEhrResponseData.

private Optional<EhrResponseData> buildEhrResponseData(UUID ehrId, Action create, String contentType) {
    // check for valid format header to produce content accordingly
    CompositionFormat format;
    if (contentType == null) {
        // assign default if no header was set
        format = CompositionFormat.RAW;
    } else {
        // if header was set process it
        MediaType mediaType = MediaType.parseMediaType(contentType);
        if (mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
            format = CompositionFormat.RAW;
        } else if (mediaType.isCompatibleWith(MediaType.APPLICATION_XML)) {
            format = CompositionFormat.XML;
        } else {
            throw new InvalidApiParameterException("Wrong Content-Type header in request");
        }
    }
    // Optional<EhrStatusDto> ehrStatus = ehrService.getEhrStatus(ehrId, CompositionFormat.FLAT);    // older, keep until rework of formatting
    Optional<EhrStatusDto> ehrStatus = ehrService.getEhrStatusEhrScape(ehrId, format);
    if (!ehrStatus.isPresent()) {
        return Optional.empty();
    }
    EhrResponseData responseData = new EhrResponseData();
    responseData.setAction(create);
    responseData.setEhrId(ehrId);
    responseData.setEhrStatus(ehrStatus.get());
    RestHref url = new RestHref();
    // TODO: use config file or alike to set the basic api path
    url.setUrl(getBaseEnvLinkURL() + "/rest/ecis/v1/ehr/" + responseData.getEhrId());
    Meta meta = new Meta();
    meta.setHref(url);
    responseData.setMeta(meta);
    return Optional.of(responseData);
}
Also used : InvalidApiParameterException(org.ehrbase.api.exception.InvalidApiParameterException) CompositionFormat(org.ehrbase.response.ehrscape.CompositionFormat) Meta(org.ehrbase.rest.ehrscape.responsedata.Meta) EhrStatusDto(org.ehrbase.response.ehrscape.EhrStatusDto) RestHref(org.ehrbase.rest.ehrscape.responsedata.RestHref) MediaType(org.springframework.http.MediaType) EhrResponseData(org.ehrbase.rest.ehrscape.responsedata.EhrResponseData)

Example 5 with CompositionFormat

use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.

the class BaseController method extractCompositionFormat.

/**
 * Extracts the {@link CompositionFormat} from the REST request's input {@link MediaType} style
 * content type header string.
 *
 * @param contentType String representation of REST request's input {@link MediaType} style
 *                    content type header
 * @return {@link CompositionFormat} expressing the content type
 * @throws NotAcceptableException when content type is not supported or input is invalid
 */
protected CompositionFormat extractCompositionFormat(String contentType) {
    final CompositionFormat compositionFormat;
    MediaType mediaType = resolveContentType(contentType);
    if (mediaType.isCompatibleWith(MediaType.APPLICATION_XML)) {
        compositionFormat = CompositionFormat.XML;
    } else if (mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
        compositionFormat = CompositionFormat.JSON;
    } else {
        throw new NotAcceptableException("Only compositions in XML or JSON are supported at the moment");
    }
    return compositionFormat;
}
Also used : CompositionFormat(org.ehrbase.response.ehrscape.CompositionFormat) NotAcceptableException(org.ehrbase.api.exception.NotAcceptableException) MediaType(org.springframework.http.MediaType)

Aggregations

CompositionFormat (org.ehrbase.response.ehrscape.CompositionFormat)6 ObjectVersionId (com.nedap.archie.rm.support.identification.ObjectVersionId)3 HttpHeaders (org.springframework.http.HttpHeaders)3 MediaType (org.springframework.http.MediaType)3 Composition (com.nedap.archie.rm.composition.Composition)2 Optional (java.util.Optional)2 UUID (java.util.UUID)2 InternalServerException (org.ehrbase.api.exception.InternalServerException)2 NotAcceptableException (org.ehrbase.api.exception.NotAcceptableException)2 ObjectNotFoundException (org.ehrbase.api.exception.ObjectNotFoundException)2 CompositionDto (org.ehrbase.response.ehrscape.CompositionDto)2 StructuredString (org.ehrbase.response.ehrscape.StructuredString)2 CompositionResponseData (org.ehrbase.response.openehr.CompositionResponseData)2 ResponseEntity (org.springframework.http.ResponseEntity)2 HierObjectId (com.nedap.archie.rm.support.identification.HierObjectId)1 ObjectRef (com.nedap.archie.rm.support.identification.ObjectRef)1 URI (java.net.URI)1 LocalDateTime (java.time.LocalDateTime)1 OffsetDateTime (java.time.OffsetDateTime)1 ZoneId (java.time.ZoneId)1