Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static uk.nhs.adaptors.gpc.consumer.gpc.InteractionIds.DOCUMENT_MIGRATE_ID;
import static uk.nhs.adaptors.gpc.consumer.gpc.InteractionIds.DOCUMENT_READ_ID;
Expand Down Expand Up @@ -70,6 +72,23 @@ public class SdsFilter implements GlobalFilter, Ordered {
private final SdsClient sdsClient;
private Map<String, BiFunction<String, String, Mono<SdsClient.SdsResponseData>>> sdsRequestFunctions;

@Override
public int getOrder() {
return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 1;
}

@PostConstruct
public void initializeSdsRequestFunctions() {
sdsRequestFunctions = Map.of(
STRUCTURED_ID, sdsClient::callForGetStructuredRecord,
PATIENT_SEARCH_ID, sdsClient::callForPatientSearchAccessDocument,
DOCUMENT_SEARCH_ID, sdsClient::callForSearchForDocumentRecord,
DOCUMENT_READ_ID, sdsClient::callForRetrieveDocumentRecord,
DOCUMENT_MIGRATE_ID, sdsClient::callForMigrateDocumentRecord,
MIGRATE_STRUCTURED_ID, sdsClient::callForMigrateStructuredRecord
);
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return getGpcProviderEndpointDetails(exchange)
Expand All @@ -82,18 +101,18 @@ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
}

private Mono<SdsClient.SdsResponseData> getGpcProviderEndpointDetails(ServerWebExchange exchange) {

return performGpcProviderSdsLookup(exchange)
.doOnNext(v -> {
if (exchange.getRequest().getPath().value().endsWith(DOCUMENT_REFERENCE_SUFFIX)) {
QueryParamsEncoder.encodeQueryParams(exchange);
}
});
.doOnNext(v -> encodeQueryParamsToUrlWhenDocumentRequest(exchange));
}

private static void encodeQueryParamsToUrlWhenDocumentRequest(ServerWebExchange exchange) {
if (exchange.getRequest().getPath().value().endsWith(DOCUMENT_REFERENCE_SUFFIX)) {
QueryParamsEncoder.encodeQueryParams(exchange);
}
}

@NotNull
private Mono<SdsClient.SdsResponseData> performGpcProviderSdsLookup(ServerWebExchange exchange) {

LoggingUtil.info(LOGGER, exchange, "Using SDS API for GP connect provider service lookup");

var id = extractHeaderValueOrThrowSdsException(exchange.getRequest().getHeaders(), SSP_INTERACTION_ID);
Expand All @@ -107,22 +126,34 @@ private String extractHeaderValueOrThrowSdsException(HttpHeaders httpHeaders, St
}

private Mono<SdsClient.SdsResponseData> performGpcProviderSdsLookup(ServerWebExchange exchange, String interactionId) {

ServerHttpRequest serverHttpRequest = exchange.getRequest();
String organisation = extractOdsCode(serverHttpRequest.getPath());
var sspTraceId = extractHeaderValueOrThrowSdsException(exchange.getRequest().getHeaders(), SSP_TRACE_ID);

return performRequestAccordingToInteractionId(interactionId, organisation, sspTraceId, exchange)
.switchIfEmpty(Mono.error(new SdsException(
String.format("No endpoint found in SDS for GP Connect endpoint InteractionId=%s OdsCode=%s",
interactionId,
organisation)))
).doOnNext(response -> {
LoggingUtil.info(LOGGER, exchange, "Found GP connect provider endpoint in sds: {}", response.getAddress());
prepareLookupUri(response.getAddress(), serverHttpRequest)
.ifPresent(uri -> exchange.getAttributes()
.put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, uri));
});
.switchIfEmpty(buildNoEndpointFoundError(interactionId, organisation)
).doOnNext(response -> updateGatewayRequestUrlIfEndpointFound(
exchange,
response,
serverHttpRequest
));
}

private void updateGatewayRequestUrlIfEndpointFound(
ServerWebExchange exchange,
SdsClient.SdsResponseData response,
ServerHttpRequest serverHttpRequest
) {
LoggingUtil.info(LOGGER, exchange, "Found GP connect provider endpoint in sds: {}", response.getAddress());
URI lookupUri = prepareLookupUri(response.getAddress(), serverHttpRequest);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, lookupUri);
}

private static @NotNull Mono<SdsClient.SdsResponseData> buildNoEndpointFoundError(String interactionId, String organisation) {
return Mono.error(new SdsException(
"No endpoint found in SDS for GP Connect endpoint InteractionId=%s OdsCode=%s".formatted(
interactionId,
organisation)));
}

private Mono<Void> appendSspHeadersToExchangeIfRequired(
Expand Down Expand Up @@ -157,21 +188,31 @@ private Mono<? extends Void> addMissingSspHeaders(
String spineCode = mapWebClientExceptionToSpineCode(e);
String fhirCode = mapSpineCodeToFhirCode(spineCode);

ResponseEntity<String> errorResponse = OperationOutcomes.buildErrorResponse(status, spineCode, fhirCode, e.getMessage());
ServerHttpResponse response = exchange.getResponse();
byte[] bytes = buildResponseBodyAsUtf8Bytes(e, status, spineCode, fhirCode, response);

DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}

private static byte @NotNull [] buildResponseBodyAsUtf8Bytes(
Exception e,
HttpStatus status,
String spineCode,
String fhirCode,
ServerHttpResponse response
) {
ResponseEntity<String> errorResponse = OperationOutcomes.buildErrorResponse(status, spineCode, fhirCode, e.getMessage());
response.setStatusCode(errorResponse.getStatusCode());
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = Objects.requireNonNullElse(errorResponse.getBody(), "");
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
return body.getBytes(StandardCharsets.UTF_8);
}

private static HttpStatus mapExceptionToHttpStatus(Exception e) {
return switch (e) {
case WebClientRequestException ignored -> HttpStatus.BAD_GATEWAY;
case WebClientResponseException webClientResponseEx ->
HttpStatus.resolve(webClientResponseEx.getStatusCode().value());
case WebClientResponseException webClientResponseEx -> HttpStatus.resolve(webClientResponseEx.getStatusCode().value());
default -> HttpStatus.INTERNAL_SERVER_ERROR;
};
}
Expand Down Expand Up @@ -225,58 +266,44 @@ private ServerWebExchange appendSspHeaderWhenAbsent(ServerWebExchange exchange,
return exchange.mutate().request(mutateRequest).build();
}


@Override
public int getOrder() {
return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 1;
}

@PostConstruct
@SuppressWarnings("unused")
public void initializeSdsRequestFunctions() {
sdsRequestFunctions = Map.of(
STRUCTURED_ID, sdsClient::callForGetStructuredRecord,
PATIENT_SEARCH_ID, sdsClient::callForPatientSearchAccessDocument,
DOCUMENT_SEARCH_ID, sdsClient::callForSearchForDocumentRecord,
DOCUMENT_READ_ID, sdsClient::callForRetrieveDocumentRecord,
DOCUMENT_MIGRATE_ID, sdsClient::callForMigrateDocumentRecord,
MIGRATE_STRUCTURED_ID, sdsClient::callForMigrateStructuredRecord
);
}

private Mono<SdsClient.SdsResponseData> performRequestAccordingToInteractionId(
String interactionId,
String organisation,
String sspTraceId,
ServerWebExchange exchange
) {
if (sdsRequestFunctions.containsKey(interactionId)) {
LoggingUtil.info(LOGGER, exchange, "Performing request with organisation \"{}\" and NHS service endpoint id \"{}\"",
organisation, interactionId);
return sdsRequestFunctions.get(interactionId)
.apply(organisation, sspTraceId);
LoggingUtil.info(
LOGGER,
exchange,
"Performing request with organisation \"{}\" and NHS service endpoint id \"{}\"",
organisation,
interactionId
);
return sdsRequestFunctions.get(interactionId).apply(organisation, sspTraceId);
}
throw new IllegalArgumentException(String.format("Not recognised InteractionId %s", interactionId));
}

private Optional<URI> prepareLookupUri(String serviceRootUrl, ServerHttpRequest originalRequest) {
var originalRequestPath = originalRequest.getPath();
var originalRequestPathValues = originalRequestPath.elements().stream()
.map(PathContainer.Element::value)
.toList();
int indexOfPatientInFhirPath = originalRequestPathValues.lastIndexOf("Patient");
int indexOfBinaryInFhirPath = originalRequestPathValues.lastIndexOf("Binary");
int indexOfStartOfFhirPath = Math.max(indexOfPatientInFhirPath, indexOfBinaryInFhirPath);
if (indexOfStartOfFhirPath < 0) {
throw new SdsFilterException("Unable to detect a supported FHIR path in the original request");
}
String fhirRequestPathPart = originalRequest.getPath().subPath(indexOfStartOfFhirPath - 1)
.toString();
String uriWithoutQueryParameters = serviceRootUrl + fhirRequestPathPart;
URI constructedUri = UriComponentsBuilder.fromUriString(uriWithoutQueryParameters)
private URI prepareLookupUri(String serviceRootUrl, ServerHttpRequest originalRequest) {
var path = getEitherBinaryOrPatientFhirPathOrThrow(originalRequest);

return UriComponentsBuilder.fromUriString(serviceRootUrl + path)
.queryParams(originalRequest.getQueryParams())
.build()
.toUri();
return Optional.of(constructedUri);

}

private static @NotNull String getEitherBinaryOrPatientFhirPathOrThrow(ServerHttpRequest originalRequest) {
var pattern = Pattern.compile("Patient|Binary");
Matcher matcher = pattern.matcher(originalRequest.getPath().toString());

if (!matcher.find()) {
throw new SdsFilterException("Unable to detect a supported FHIR path in the original request");
}

var startIndex = Math.max(0, matcher.start() - 1);
return originalRequest.getPath().toString().substring(startIndex);
}
}
Loading