Skip to content
Open
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 Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM hapiproject/hapi:v8.10.0-1 AS base
FROM hapiproject/hapi:v8.10.0-3 AS base
FROM eclipse-temurin:21-jdk

# Copy the war from the base image
Expand Down
2 changes: 2 additions & 0 deletions hapi.application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ logging:
level:
ca.uhn.fhir.jpa.packages: DEBUG
ca.uhn.fhir.log.terminology_troubleshooting: DEBUG
com.nhs: DEBUG


server:
Expand Down Expand Up @@ -529,3 +530,4 @@ hapi:
refuse_to_fetch_third_party_urls: false
fhir_version: R4


82 changes: 82 additions & 0 deletions src/main/java/com/nhs/CachedBodyHttpServletRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.nhs;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
* Buffers the request body so it can be read, inspected, and optionally replaced
* before the request continues down the filter chain to the real servlet.
*/
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

private byte[] myBody;

public CachedBodyHttpServletRequest(HttpServletRequest theRequest) throws IOException {
super(theRequest);
try (var in = theRequest.getInputStream()) {
myBody = in.readAllBytes();
}
}

public byte[] getBodyBytes() {
return myBody;
}

public String getBodyAsString() {
return new String(myBody, StandardCharsets.UTF_8);
}

/** Replace the buffered body - subsequent reads (by the real servlet) will see this instead. */
public void setBody(byte[] theNewBody) {
myBody = theNewBody;
}

@Override
public int getContentLength() {
return myBody.length;
}

@Override
public long getContentLengthLong() {
return myBody.length;
}

@Override
public ServletInputStream getInputStream() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(myBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener theReadListener) {
// not needed for synchronous use
}

@Override
public int read() {
return byteArrayInputStream.read();
}
};
}

@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/nhs/CapabilityStatementValidationConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.nhs;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CapabilityStatementValidationConfig {

@Autowired
private FhirContext myFhirContext;

@Autowired
private DaoRegistry myDaoRegistry;

@Bean
public FilterRegistrationBean<ValidateProfileDefaultingFilter> validateProfileDefaultingFilter() {
ValidateProfileDefaultingFilter filter =
new ValidateProfileDefaultingFilter(myFhirContext, myDaoRegistry);

FilterRegistrationBean<ValidateProfileDefaultingFilter> registration =
new FilterRegistrationBean<>(filter);

registration.addUrlPatterns("/fhir/*");
registration.setOrder(1);
registration.setName("validateProfileDefaultingFilter");

System.out.println("[CONFIG] ValidateProfileDefaultingFilter registered for /fhir/*");
return registration;
}
}
19 changes: 16 additions & 3 deletions src/main/java/com/nhs/OpenApiCustomizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,21 @@ protected void doFilterInternal(
String originalBody = new String(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
String contentType = wrapper.getContentType();
String acceptHeader = request.getHeader("Accept");
boolean isYaml = (contentType != null && contentType.contains("yaml"))
|| (acceptHeader != null && acceptHeader.contains("yaml"))
|| !request.getRequestURI().contains("format=json");
boolean isYaml;
if (contentType != null && contentType.contains("json")) {
isYaml = false;
} else if (contentType != null && contentType.contains("yaml")) {
isYaml = true;
} else if (acceptHeader != null && acceptHeader.contains("json")) {
isYaml = false;
} else if (acceptHeader != null && acceptHeader.contains("yaml")) {
isYaml = true;
} else {
// No explicit signal from Content-Type, Accept, or query param —
// HAPI 8.10 defaults to YAML here, but Swagger UI (and most tooling)
// assumes JSON unless told otherwise. Force JSON as our default.
isYaml = request.getRequestURI().contains("format=yaml");
}

try {
ObjectMapper reader = isYaml ? yamlMapper : jsonMapper;
Expand All @@ -58,6 +70,7 @@ protected void doFilterInternal(
addXmlContentTypes(root);

byte[] modified = writer.writeValueAsBytes(root);
response.setContentType(isYaml ? "application/yaml" : "application/json");
response.setContentLength(modified.length);
response.getOutputStream().write(modified);

Expand Down
Loading