Skip to content

Commit

Permalink
[CDR-1471] Align template controller with latest spec (#1334)
Browse files Browse the repository at this point in the history
* Align template controller with latest spec

* Add suggestion from Code review
  • Loading branch information
alexlehn committed Jun 21, 2024
1 parent 6b16a76 commit 48753b2
Show file tree
Hide file tree
Showing 8 changed files with 387 additions and 329 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Added
- Create a `ehrbase` user to run the Docker container ([#1336](https://github.com/ehrbase/ehrbase/pull/1336))
### Changed
- Deprecate plugin aspects ([#1344](https://github.com/ehrbase/ehrbase/pull/1344))
* Deprecate plugin aspects ([#1344](https://github.com/ehrbase/ehrbase/pull/1344))
* Add simplified JSON-based “web template” format support for GET Template ADL 1.4 using header `Accept: application/openehr.wt+json` ([1334](https://github.com/ehrbase/ehrbase/pull/1334))
### Fixed

## [2.4.0]
Expand Down
119 changes: 22 additions & 97 deletions rest-openehr/src/main/java/org/ehrbase/rest/BaseController.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@
*/
package org.ehrbase.rest;

import com.google.common.annotations.VisibleForTesting;
import com.nedap.archie.rm.datavalues.quantity.datetime.DvDateTime;
import java.net.URI;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.ehrbase.api.exception.InternalServerException;
import org.ehrbase.api.exception.InvalidApiParameterException;
import org.ehrbase.api.exception.NotAcceptableException;
import org.ehrbase.api.exception.ObjectNotFoundException;
Expand All @@ -37,8 +35,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
Expand All @@ -65,32 +62,15 @@ public abstract class BaseController {
public static final String CONTENT_TYPE = HttpHeaders.CONTENT_TYPE;
public static final String ACCEPT = HttpHeaders.ACCEPT;
public static final String REQ_CONTENT_TYPE = "Client may request content format";
public static final String REQ_CONTENT_TYPE_BODY = "Format of transferred body";
public static final String REQ_ACCEPT = "Client should specify expected format";
// response headers
public static final String RESP_CONTENT_TYPE_DESC = "Format of response";
// Audit
public static final String REST_OPERATION = "RestOperation";

public static final String LOCATION = HttpHeaders.LOCATION;
public static final String ETAG = HttpHeaders.ETAG;
public static final String LAST_MODIFIED = HttpHeaders.LAST_MODIFIED;

public static final String IF_MATCH = HttpHeaders.IF_MATCH;
public static final String IF_NONE_MATCH = HttpHeaders.IF_NONE_MATCH;
// Configuration of swagger-ui description fields
// request headers
public static final String REQ_OPENEHR_VERSION = "Optional custom request header for versioning";
public static final String REQ_OPENEHR_AUDIT = "Optional custom request header for auditing";
public static final String REQ_PREFER = "May be used by clients for resource representation negotiation";
public static final String RESP_LOCATION_DESC = "Location of resource";
public static final String RESP_ETAG_DESC = "Entity tag for resource";
public static final String RESP_LAST_MODIFIED_DESC = "Time of last modification of resource";
// common response description fields
public static final String RESP_NOT_ACCEPTABLE_DESC =
"Not Acceptable - Service can not fulfill requested format via accept header.";
public static final String RESP_UNSUPPORTED_MEDIA_DESC =
"Unsupported Media Type - request's content-type not supported.";

// constants of all API resources
public static final String EHR = "ehr";
Expand All @@ -107,23 +87,6 @@ public abstract class BaseController {
public static final String API_CONTEXT_PATH_WITH_VERSION = API_CONTEXT_PATH + "/v1";
public static final String ADMIN_API_CONTEXT_PATH = "${admin-api.context-path:/rest/admin}";

public Map<String, Map<String, String>> add2MetaMap(
Map<String, Map<String, String>> metaMap, String key, String value) {
Map<String, String> contentMap;

if (metaMap == null) {
metaMap = new HashMap<>();
contentMap = new HashMap<>();
metaMap.put("meta", contentMap);
} else {
contentMap = metaMap.get("meta");
}

contentMap.put(key, value);
return metaMap;
}

@VisibleForTesting
public String getContextPath() {
return ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString();
}
Expand All @@ -140,7 +103,7 @@ public String getContextPath() {
*/
protected URI createLocationUri(String... pathSegments) {
return UriComponentsBuilder.fromHttpUrl(getContextPath())
.path(this.encodePath(apiContextPathWithVersion))
.path(UriUtils.encodePath(apiContextPathWithVersion, "UTF-8"))
.pathSegment(pathSegments)
.build()
.toUri();
Expand Down Expand Up @@ -220,10 +183,9 @@ protected CompositionRepresentation extractCompositionRepresentation(String cont
final MediaType mediaType = resolveContentType(
contentType,
MediaType.APPLICATION_JSON,
mediaType1 -> mediaType1.isCompatibleWith(MediaType.APPLICATION_JSON)
|| mediaType1.isCompatibleWith(MediaType.APPLICATION_XML)
|| mediaType1.isCompatibleWith(OpenEHRMediaType.APPLICATION_WT_FLAT_SCHEMA_JSON)
|| mediaType1.isCompatibleWith(OpenEHRMediaType.APPLICATION_WT_STRUCTURED_SCHEMA_JSON));
MediaType.APPLICATION_XML,
OpenEHRMediaType.APPLICATION_WT_FLAT_SCHEMA_JSON,
OpenEHRMediaType.APPLICATION_WT_STRUCTURED_SCHEMA_JSON);

representation =
CompositionRepresentation.selectFromMediaTypeWithFormat(mediaType, parsedFormat.orElse(null));
Expand All @@ -235,25 +197,11 @@ protected CompositionRepresentation extractCompositionRepresentation(String cont
return representation;
}

/**
* Convenience helper to encode path strings to URI-safe strings
*
* @param path input
* @return URI-safe escaped string
* @throws InternalServerException when encoding failed
*/
public String encodePath(String path) {

path = UriUtils.encodePath(path, "UTF-8");

return path;
}

/**
* Extracts the UUID base from a versioned UID. Or, if
*
* @param versionUid
* @return
* @param versionUid raw versionUid in format <code>[UUID]::[VERSION]</code>
* @return uuid <code>[UUID]</code> part
*/
protected UUID extractVersionedObjectUidFromVersionUid(String versionUid) {
if (!versionUid.contains("::")) {
Expand All @@ -265,69 +213,46 @@ protected UUID extractVersionedObjectUidFromVersionUid(String versionUid) {
protected Optional<Integer> extractVersionFromVersionUid(String versionUid) {
// extract the version from string of format "$UUID::$SYSTEM::$VERSION"
// via making a substring starting at last occurrence of "::" + 2
int lastOccourence = versionUid.lastIndexOf("::");
if (lastOccourence > 0 && versionUid.indexOf("::") != lastOccourence) {
return Optional.of(Integer.parseInt(versionUid.substring(lastOccourence + 2)));
int lastOccurrence = versionUid.lastIndexOf("::");
if (lastOccurrence > 0 && versionUid.indexOf("::") != lastOccurrence) {
return Optional.of(Integer.parseInt(versionUid.substring(lastOccurrence + 2)));
}

return Optional.empty();
}

/**
* Add attribute to the current request.
*
* @param attributeName
* @param value
*/
protected void enrichRequestAttribute(String attributeName, Object value) {
RequestContextHolder.currentRequestAttributes()
.setAttribute(attributeName, value, RequestAttributes.SCOPE_REQUEST);
}

/**
* Resolves the Content-Type based on Accept header.
* Resolves the Content-Type based on Accept header. Validates if the given <code>acceptHeader</code> in
* either <code>application/json</code> or <code>application/xml</code>.
* In case <code>acceptHeader</code> is given <code>application/json</code> will be selected as a default.
*
* @param acceptHeader Accept header value
* @return Content-Type of the response
*/
protected MediaType resolveContentType(String acceptHeader) {
return resolveContentType(acceptHeader, MediaType.APPLICATION_JSON);
}

/**
* Resolves the Content-Type based on Accept header.
*
* @param acceptHeader Accept header value
* @param defaultMediaType Default Content-Type
* @return Content-Type of the response
*/
protected MediaType resolveContentType(String acceptHeader, MediaType defaultMediaType) {
return resolveContentType(
acceptHeader,
defaultMediaType,
mediaType -> mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)
|| mediaType.isCompatibleWith(MediaType.APPLICATION_XML));
return resolveContentType(acceptHeader, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML);
}

/**
* Resolves the Content-Type based on Accept header using the supported predicate.
*
* @param acceptHeader Accept header value
* @param defaultMediaType Default Content-Type
* @param isSupported Filter the supported Content-Types
* @param acceptHeader Accept header value
* @param defaultMediaType Default Content-Type
* @param supportedMediaTypes supported Content-Types
* @return Content-Type of the response
*/
protected MediaType resolveContentType(
String acceptHeader, MediaType defaultMediaType, Predicate<MediaType> isSupported) {
String acceptHeader, MediaType defaultMediaType, MediaType... supportedMediaTypes) {

List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);
if (mediaTypes.isEmpty()) {
return defaultMediaType;
}

MediaType.sortBySpecificityAndQuality(mediaTypes);
MimeTypeUtils.sortBySpecificity(mediaTypes);
MediaType contentType = mediaTypes.stream()
.filter(isSupported)
.filter(type -> Stream.concat(Stream.of(defaultMediaType), Arrays.stream(supportedMediaTypes))
.anyMatch(type::isCompatibleWith))
.findFirst()
.orElseThrow(() -> new InvalidApiParameterException("Wrong Content-Type header in request"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ private ResponseEntity<DirectoryResponseData> createDirectoryResponse(
DirectoryResponseData body;

if (prefer != null && prefer.equals(RETURN_REPRESENTATION)) {
headers.setContentType(resolveContentType(accept, MediaType.APPLICATION_XML));
headers.setContentType(resolveContentType(accept));
body = buildResponse(folderDto);
successStatus = getSuccessStatus(method);
} else {
Expand Down
Loading

0 comments on commit 48753b2

Please sign in to comment.