Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</parent>

<artifactId>server</artifactId>
<version>2.24.0</version>
<version>2.24.0</version>
<packaging>pom</packaging>
<name>EHRbase server</name>
<description>EHRbase openEHR server</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,14 @@ public ResponseEntity<?> createComposition(
.orElse(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
}


@PostMapping(value = "/composition/validate")
@Override
public ResponseEntity<Void> validateComposition(
@RequestHeader(value = CONTENT_TYPE) String contentType,
@RequestHeader(value = ACCEPT, required = false) String accept,
@RequestParam(value = "templateId", required = false) String templateId,
@RequestParam(value = "format", required = false) String format,
@RequestBody String composition
) {
@RequestBody String composition) {
var requestRepresentation = extractCompositionRepresentation(contentType, format);
var compoObj = compositionService.buildComposition(composition, requestRepresentation.format, templateId);

Expand All @@ -170,14 +168,13 @@ public ResponseEntity<Void> validateComposition(

@PostMapping(value = "{ehr_id}/composition/preview")
@Override
public ResponseEntity<Map<String, Object>> previewComposition(
public ResponseEntity<Map<String, Object>> previewComposition(
@RequestHeader(value = CONTENT_TYPE) String contentType,
@RequestHeader(value = ACCEPT, required = false) String accept,
@PathVariable(value = "ehr_id") String ehrIdString,
@RequestParam(value = "templateId", required = false) String templateId,
@RequestParam(value = "format", required = false) String format,
@RequestBody String composition
) {
@RequestBody String composition) {
var ehrId = getEhrUuid(ehrIdString);
var requestRepresentation = extractCompositionRepresentation(contentType, format);
var compoObj = compositionService.buildComposition(composition, requestRepresentation.format, templateId);
Expand All @@ -187,46 +184,46 @@ public ResponseEntity<Map<String, Object>> previewComposition(
return ResponseEntity.ok(compDataPreview);
}

@PutMapping(
value = "{ehr_id}/composition/{versioned_object_uid}/preview",
consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE,
OpenEHRMediaType.APPLICATION_WT_FLAT_SCHEMA_JSON_VALUE,
OpenEHRMediaType.APPLICATION_WT_STRUCTURED_SCHEMA_JSON_VALUE
})
@Override
public ResponseEntity<Map<String, Object>> previewUpdatedComposition(
@RequestHeader(value = CONTENT_TYPE) String contentType,
@RequestHeader(value = ACCEPT, required = false) String accept,
@RequestHeader(value = IF_MATCH) String ifMatch,
@PathVariable(value = "ehr_id") String ehrIdString,
@PathVariable(value = "versioned_object_uid") String versionedObjectUidString,
@RequestParam(value = "templateId", required = false) String templateId,
@RequestParam(value = "format", required = false) String format,
@RequestBody String composition) {

UUID ehrId = getEhrUuid(ehrIdString);
UUID versionedObjectUid = getCompositionVersionedObjectUidString(versionedObjectUidString);

CompositionRepresentation requestRepresentation = extractCompositionRepresentation(contentType, format);
Composition compoObj =
compositionService.buildComposition(composition, requestRepresentation.format, templateId);

Optional<String> inputUuid = getUidFrom(compoObj);
inputUuid.ifPresent(id -> {
if (!versionedObjectUid.equals(extractVersionedObjectUidFromVersionUid(id))) {
throw new PreconditionFailedException(
"UUID from input must match given versioned_object_uid in request URL");
}
});

ObjectVersionId ifMatchId = new ObjectVersionId(ifMatch);
Map<String, Object> compDataPreview =
compositionService.previewUpdatedCompDataRecords(ehrId, ifMatchId, compoObj);

return ResponseEntity.ok(compDataPreview);
}
@PutMapping(
value = "{ehr_id}/composition/{versioned_object_uid}/preview",
consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE,
OpenEHRMediaType.APPLICATION_WT_FLAT_SCHEMA_JSON_VALUE,
OpenEHRMediaType.APPLICATION_WT_STRUCTURED_SCHEMA_JSON_VALUE
})
@Override
public ResponseEntity<Map<String, Object>> previewUpdatedComposition(
@RequestHeader(value = CONTENT_TYPE) String contentType,
@RequestHeader(value = ACCEPT, required = false) String accept,
@RequestHeader(value = IF_MATCH) String ifMatch,
@PathVariable(value = "ehr_id") String ehrIdString,
@PathVariable(value = "versioned_object_uid") String versionedObjectUidString,
@RequestParam(value = "templateId", required = false) String templateId,
@RequestParam(value = "format", required = false) String format,
@RequestBody String composition) {

UUID ehrId = getEhrUuid(ehrIdString);
UUID versionedObjectUid = getCompositionVersionedObjectUidString(versionedObjectUidString);

CompositionRepresentation requestRepresentation = extractCompositionRepresentation(contentType, format);
Composition compoObj =
compositionService.buildComposition(composition, requestRepresentation.format, templateId);

Optional<String> inputUuid = getUidFrom(compoObj);
inputUuid.ifPresent(id -> {
if (!versionedObjectUid.equals(extractVersionedObjectUidFromVersionUid(id))) {
throw new PreconditionFailedException(
"UUID from input must match given versioned_object_uid in request URL");
}
});

ObjectVersionId ifMatchId = new ObjectVersionId(ifMatch);
Map<String, Object> compDataPreview =
compositionService.previewUpdatedCompDataRecords(ehrId, ifMatchId, compoObj);

return ResponseEntity.ok(compDataPreview);
}

@PutMapping(
value = "/{ehr_id}/composition/{versioned_object_uid}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@
import org.ehrbase.openehr.sdk.aql.dto.select.SelectExpression;
import org.ehrbase.openehr.sdk.response.dto.MetaData;
import org.ehrbase.openehr.sdk.response.dto.QueryResponseData;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.CompositionDto;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.CompositionFormat;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.QueryDefinitionResultDto;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.QueryResultDto;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.query.ResultHolder;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.CompositionDto;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.StructuredString;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.CompositionFormat;
import org.ehrbase.openehr.sdk.response.dto.ehrscape.query.ResultHolder;
import org.ehrbase.rest.BaseController;
import org.ehrbase.rest.openehr.dto.CompositionQueryResponse;
import org.ehrbase.rest.openehr.format.CompositionRepresentation;
Expand Down Expand Up @@ -192,24 +192,22 @@ public ResponseEntity<CompositionQueryResponse> executeCompositionQuery(

PreparedAdHocQuery preparedQuery = prepareAdHocQuery(requestBody);

validateCompositionSelect(preparedQuery);
validateCompositionSelect(preparedQuery);

String effectiveFormat = Optional.ofNullable(format)
.filter(StringUtils::isNotBlank)
.orElseGet(() -> Optional.ofNullable(requestBody)
.map(body -> body.get(COMPOSITION_FORMAT_PARAM))
.map(Object::toString)
.orElse(null));
String effectiveFormat = Optional.ofNullable(format)
.filter(StringUtils::isNotBlank)
.orElseGet(() -> Optional.ofNullable(requestBody)
.map(body -> body.get(COMPOSITION_FORMAT_PARAM))
.map(Object::toString)
.orElse(null));

CompositionRepresentation representation = extractCompositionRepresentation(accept, effectiveFormat);

QueryResultDto result = aqlQueryService.query(preparedQuery.queryRequest());

CompositionQueryResponse response = toCompositionResponse(preparedQuery, result, representation);

return ResponseEntity.ok()
.contentType(representation.mediaType)
.body(response);
return ResponseEntity.ok().contentType(representation.mediaType).body(response);
}

/**
Expand Down Expand Up @@ -313,26 +311,26 @@ private PreparedAdHocQuery prepareAdHocQuery(Map<String, Object> requestBody) {
logger.debug("Got following input: {}", body);

Object rawQuery = body.get(Q_PARAM);
String queryText = switch (rawQuery) {
case null -> throw new InvalidApiParameterException("No query provided.");
case Collection<?> __ -> throw new InvalidApiParameterException("Multiple queries provided.");
case String s when StringUtils.isBlank(s) -> throw new InvalidApiParameterException("No query provided.");
case String s -> s;
default -> throw new InvalidApiParameterException("Data type of query not supported.");
};
String queryText =
switch (rawQuery) {
case null -> throw new InvalidApiParameterException("No query provided.");
case Collection<?> __ -> throw new InvalidApiParameterException("Multiple queries provided.");
case String s
when StringUtils.isBlank(s) -> throw new InvalidApiParameterException("No query provided.");
case String s -> s;
default -> throw new InvalidApiParameterException("Data type of query not supported.");
};

HttpRestContext.register(QUERY_EXECUTE_ENDPOINT, Boolean.TRUE);

Map<String, Object> params = OpenEhrQueryRequestUtils.getSubMap(body, QUERY_PARAMETERS);

Long fetch = OpenEhrQueryRequestUtils.getOptionalLong(body, FETCH_PARAM).orElse(null);
Long offset = OpenEhrQueryRequestUtils.getOptionalLong(body, OFFSET_PARAM).orElse(null);
Long offset =
OpenEhrQueryRequestUtils.getOptionalLong(body, OFFSET_PARAM).orElse(null);

AqlQueryRequest queryRequest = AqlQueryRequest.prepare(
queryText,
OpenEhrQueryRequestUtils.rewriteExplicitParameterTypes(params),
fetch,
offset);
queryText, OpenEhrQueryRequestUtils.rewriteExplicitParameterTypes(params), fetch, offset);

return new PreparedAdHocQuery(queryText, queryRequest);
}
Expand All @@ -342,9 +340,11 @@ private SelectExpression validateCompositionSelect(PreparedAdHocQuery preparedQu
SelectClause selectClause = Optional.ofNullable(aqlQuery.getSelect())
.orElseThrow(() -> new InvalidApiParameterException("Query must contain a SELECT clause."));

List<SelectExpression> statements = Optional.ofNullable(selectClause.getStatement()).orElse(List.of());
List<SelectExpression> statements =
Optional.ofNullable(selectClause.getStatement()).orElse(List.of());
if (statements.size() != 1) {
throw new InvalidApiParameterException("Composition query endpoint supports exactly one SELECT expression.");
throw new InvalidApiParameterException(
"Composition query endpoint supports exactly one SELECT expression.");
}

SelectExpression expression = statements.getFirst();
Expand All @@ -356,7 +356,8 @@ private SelectExpression validateCompositionSelect(PreparedAdHocQuery preparedQu
if (identifiedPath.getPath() != null
&& identifiedPath.getPath().getPathNodes() != null
&& !identifiedPath.getPath().getPathNodes().isEmpty()) {
throw new InvalidApiParameterException("Composition query must select the composition root without sub-paths.");
throw new InvalidApiParameterException(
"Composition query must select the composition root without sub-paths.");
}

AbstractContainmentExpression root = identifiedPath.getRoot();
Expand All @@ -372,10 +373,11 @@ private SelectExpression validateCompositionSelect(PreparedAdHocQuery preparedQu
}

private CompositionQueryResponse toCompositionResponse(
PreparedAdHocQuery preparedQuery, QueryResultDto result, CompositionRepresentation representation) {
PreparedAdHocQuery preparedQuery, QueryResultDto result, CompositionRepresentation representation) {

MetaData meta = aqlQueryContext.createMetaData(null);
List<ResultHolder> resultSet = Optional.ofNullable(result.getResultSet()).orElse(List.of());
List<ResultHolder> resultSet =
Optional.ofNullable(result.getResultSet()).orElse(List.of());

List<CompositionQueryResponse.CompositionRow> rows = new ArrayList<>(resultSet.size());
for (ResultHolder holder : resultSet) {
Expand All @@ -394,11 +396,12 @@ private CompositionQueryResponse toCompositionResponse(
}

private CompositionQueryResponse.CompositionRow buildCompositionRow(
Composition composition, CompositionRepresentation representation) {
Composition composition, CompositionRepresentation representation) {

ObjectVersionId versionId = composition.getUid() instanceof ObjectVersionId id ? id : null;
UUID compositionId = extractCompositionId(versionId);
String versionUid = Optional.ofNullable(versionId).map(ObjectVersionId::getValue).orElse(null);
String versionUid =
Optional.ofNullable(versionId).map(ObjectVersionId::getValue).orElse(null);
Integer version = extractVersion(versionId);

String templateId = resolveTemplateId(composition, compositionId);
Expand All @@ -414,16 +417,10 @@ private CompositionQueryResponse.CompositionRow buildCompositionRow(
CompositionDto dto = new CompositionDto(composition, templateId, compositionId, null);
StructuredString serialized = compositionService.serialize(dto, representation.format);

Object content = deserializeCompositionContent(serialized, representation);
Object content = deserializeCompositionContent(serialized, representation);

return new CompositionQueryResponse.CompositionRow(
ehrId,
versionUid,
version,
templateId,
representation.format.name(),
content
);
return new CompositionQueryResponse.CompositionRow(
ehrId, versionUid, version, templateId, representation.format.name(), content);
}

private UUID extractCompositionId(ObjectVersionId versionId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
/*
* Copyright (c) 2025 vitasystems GmbH.
*
* This file is part of project EHRbase
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ehrbase.rest.openehr.dto;

import java.util.List;
Expand All @@ -7,14 +24,8 @@
/**
* Response payload for the composition-specific AQL endpoint.
*/
public record CompositionQueryResponse(
MetaData meta, String query, List<CompositionRow> compositions) {
public record CompositionQueryResponse(MetaData meta, String query, List<CompositionRow> compositions) {

public record CompositionRow(
UUID ehrId,
String compositionUid,
Integer version,
String templateId,
String format,
Object content) {}
}
UUID ehrId, String compositionUid, Integer version, String templateId, String format, Object content) {}
}
Loading
Loading