Skip to content

Commit

Permalink
issue #3192 - fhirVersion-aware CapabilityStatement
Browse files Browse the repository at this point in the history
Signed-off-by: Lee Surprenant <[email protected]>
  • Loading branch information
lmsurpre committed Jan 20, 2022
1 parent 781e45c commit 7181721
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand All @@ -41,6 +42,7 @@
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
Expand Down Expand Up @@ -122,6 +124,23 @@ public class Capabilities extends FHIRResource {
.map(rt -> ResourceType.Value.from(rt.getSimpleName()))
.collect(Collectors.toList());

private static final Set<ResourceType.Value> R4B_ONLY_RESOURCES = new HashSet<>();
{
R4B_ONLY_RESOURCES.add(ResourceType.Value.ADMINISTRABLE_PRODUCT_DEFINITION);
R4B_ONLY_RESOURCES.add(ResourceType.Value.CITATION);
R4B_ONLY_RESOURCES.add(ResourceType.Value.CLINICAL_USE_DEFINITION);
R4B_ONLY_RESOURCES.add(ResourceType.Value.EVIDENCE_REPORT);
R4B_ONLY_RESOURCES.add(ResourceType.Value.INGREDIENT);
R4B_ONLY_RESOURCES.add(ResourceType.Value.MANUFACTURED_ITEM_DEFINITION);
R4B_ONLY_RESOURCES.add(ResourceType.Value.MEDICINAL_PRODUCT_DEFINITION);
R4B_ONLY_RESOURCES.add(ResourceType.Value.NUTRITION_PRODUCT);
R4B_ONLY_RESOURCES.add(ResourceType.Value.PACKAGED_PRODUCT_DEFINITION);
R4B_ONLY_RESOURCES.add(ResourceType.Value.REGULATED_AUTHORIZATION);
R4B_ONLY_RESOURCES.add(ResourceType.Value.SUBSCRIPTION_STATUS);
R4B_ONLY_RESOURCES.add(ResourceType.Value.SUBSCRIPTION_TOPIC);
R4B_ONLY_RESOURCES.add(ResourceType.Value.SUBSTANCE_DEFINITION);
}

// Error Messages
private static final String ERROR_MSG = "Caught exception while processing 'metadata' request.";
private static final String ERROR_CONSTRUCTING = "An error occurred while constructing the Conformance statement.";
Expand All @@ -138,7 +157,7 @@ public Capabilities() throws Exception {

@GET
@Path("metadata")
public Response capabilities(@QueryParam("mode") @DefaultValue("full") String mode) {
public Response capabilities(@QueryParam("mode") @DefaultValue("full") String mode, @HeaderParam("accept") String accept) {
log.entering(this.getClass().getName(), "capabilities()");
try {
Date startTime = new Date();
Expand All @@ -148,13 +167,17 @@ public Response capabilities(@QueryParam("mode") @DefaultValue("full") String mo
throw new IllegalArgumentException("Invalid mode parameter: must be one of [full, normative, terminology]");
}

FHIRVersion fhirVersion = getFhirVersion(accept);

// Defaults to 60 minutes (or what's in the fhirConfig)
int cacheTimeout = FHIRConfigHelper.getIntProperty(PROPERTY_CAPABILITY_STATEMENT_CACHE, 60);
Configuration configuration = Configuration.of(Duration.of(cacheTimeout, ChronoUnit.MINUTES));

Map<String, Resource> cacheAsMap = CacheManager.getCacheAsMap(CAPABILITY_STATEMENT_CACHE_NAME, configuration);
CacheManager.reportCacheStats(log, CAPABILITY_STATEMENT_CACHE_NAME);
Resource capabilityStatement = cacheAsMap.computeIfAbsent(mode, k -> computeCapabilityStatement(mode));

String cacheKey = mode + "-" + fhirVersion.getValue();
Resource capabilityStatement = cacheAsMap.computeIfAbsent(cacheKey, k -> computeCapabilityStatement(mode, fhirVersion));

RestAuditLogger.logMetadata(httpServletRequest, startTime, new Date(), Response.Status.OK);

Expand All @@ -180,19 +203,38 @@ public Response capabilities(@QueryParam("mode") @DefaultValue("full") String mo
}
}

/**
* Which FHIRVersion to use for the generated CapabilityStatement
*
* @param acceptHeaderValue
* @return 4.3.0 if the client is asking for it, otherwise 4.0.1
*/
private FHIRVersion getFhirVersion(String acceptHeaderValue) {
if (acceptHeaderValue != null && !acceptHeaderValue.isEmpty()) {
for (String headerValueElement : acceptHeaderValue.split(",")) {
String requestedVersion = MediaType.valueOf(headerValueElement).getParameters().get(FHIRMediaType.FHIR_VERSION_PARAMETER);
if ("4.3".equals(requestedVersion) || "4.3.0".equals(requestedVersion)) {
// TODO: remove _CIBUILD after generating from the published 4.3.0 artifacts
return FHIRVersion.VERSION_4_3_0_CIBUILD;
}
}
}
return FHIRVersion.VERSION_4_0_1;
}

private boolean isValidMode(String mode) {
return "full".equals(mode) || "normative".equals(mode) || "terminology".equals(mode);
}

private Resource computeCapabilityStatement(String mode) {
private Resource computeCapabilityStatement(String mode, FHIRVersion fhirVersion) {
try {
switch (mode) {
case "terminology":
return buildTerminologyCapabilities();
case "full":
case "normative":
default:
return buildCapabilityStatement();
return buildCapabilityStatement(fhirVersion);
}
} catch (Exception e) {
throw new RuntimeException(e);
Expand Down Expand Up @@ -290,7 +332,7 @@ private List<TerminologyCapabilities.CodeSystem> buildCodeSystem() {
*
* @throws Exception
*/
private CapabilityStatement buildCapabilityStatement() throws Exception {
private CapabilityStatement buildCapabilityStatement(FHIRVersion fhirVersion) throws Exception {
// Retrieve the "resources" config property group.
PropertyGroup rsrcsGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);

Expand Down Expand Up @@ -344,13 +386,11 @@ private CapabilityStatement buildCapabilityStatement() throws Exception {
}

com.ibm.fhir.model.type.Boolean isUpdateCreate = com.ibm.fhir.model.type.Boolean.of(isUpdateCreateEnabled());

FHIRVersion fhirVersion = FHIRVersion.VERSION_4_0_1;

// Build the list of supported resources.
List<Rest.Resource> resources = new ArrayList<>();

List<ResourceType.Value> resourceTypes = getSupportedResourceTypes(rsrcsGroup);
List<ResourceType.Value> resourceTypes = getSupportedResourceTypes(rsrcsGroup, fhirVersion);

for (ResourceType.Value resourceType : resourceTypes) {
String resourceTypeName = resourceType.value();
Expand Down Expand Up @@ -634,18 +674,23 @@ private List<com.ibm.fhir.model.type.String> convertStringList(List<String> stri
* @return a list of resource types to support
* @throws Exception
*/
private List<ResourceType.Value> getSupportedResourceTypes(PropertyGroup rsrcsGroup) throws Exception {
private List<ResourceType.Value> getSupportedResourceTypes(PropertyGroup rsrcsGroup, FHIRVersion fhirVersion) throws Exception {
final List<ResourceType.Value> resourceTypes = new ArrayList<>();

if (rsrcsGroup == null) {
return ALL_RESOURCE_TYPES;
resourceTypes.addAll(ALL_RESOURCE_TYPES);
} else {
if (rsrcsGroup.getBooleanProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN, true)) {
resourceTypes.addAll(ALL_RESOURCE_TYPES);
} else {
resourceTypes.addAll(FHIRConfigHelper.getSupportedResourceTypes().stream()
.map(ResourceType.Value::from)
.collect(Collectors.toList()));
}
}

List<ResourceType.Value> resourceTypes = new ArrayList<>();
if (rsrcsGroup.getBooleanProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN, true)) {
resourceTypes = ALL_RESOURCE_TYPES;
} else {
resourceTypes = FHIRConfigHelper.getSupportedResourceTypes().stream()
.map(ResourceType.Value::from)
.collect(Collectors.toList());
if (fhirVersion.getValueAsEnum() == FHIRVersion.Value.VERSION_4_0_1) {
resourceTypes.removeAll(R4B_ONLY_RESOURCES);
}
return resourceTypes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
import com.ibm.fhir.server.resources.Capabilities;

public class CapabilitiesTest {
private static final boolean DEBUG = false;

@BeforeClass
void setup() {
FHIRConfiguration.setConfigHome("target/test-classes");
Expand All @@ -46,22 +44,37 @@ void testBuildCapabilityStatement_resources_omitted() throws Exception {
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities("full");
Response capabilities = c.capabilities("full", null);
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
CapabilityStatement.Rest restDefinition = capabilityStatement.getRest().get(0);

assertRestDefinition(restDefinition, 4, 128, 9, 0, 0, 9, 0, 0);
}

@Test
void testBuildCapabilityStatement_resources_empty_r4() throws Exception {
FHIRRequestContext.get().setTenantId("empty");
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities("full", "application/fhir+json;fhirVersion=4.0");
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
CapabilityStatement.Rest restDefinition = capabilityStatement.getRest().get(0);

assertRestDefinition(restDefinition, 4, 141, 9, 0, 0, 9, 0, 0);
assertRestDefinition(restDefinition, 0, 128, 0, 0, 0, 0, 0, 0);
}

@Test
void testBuildCapabilityStatement_resources_empty() throws Exception {
void testBuildCapabilityStatement_resources_empty_r4b() throws Exception {
FHIRRequestContext.get().setTenantId("empty");
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities("full");
Response capabilities = c.capabilities("full", "application/fhir+json;fhirVersion=4.3");
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
Expand All @@ -76,7 +89,7 @@ void testBuildCapabilityStatement_resources_filtered() throws Exception {
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities("full");
Response capabilities = c.capabilities("full", "");
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
Expand All @@ -88,9 +101,6 @@ void testBuildCapabilityStatement_resources_filtered() throws Exception {
private void assertRestDefinition(CapabilityStatement.Rest restDefinition, int systemInteractions, int numOfResources,
int patientInteractions, int patientIncludes, int patientRevIncludes,
int practitionerInteractions, int practitionerIncludes, int practitionerRevIncludes) {
if (DEBUG) {
System.out.println(restDefinition);
}
assertEquals(restDefinition.getResource().size(), numOfResources, "Number of supported resources");
assertFalse(restDefinition.getResource().stream().anyMatch(r -> r.getType().getValueAsEnum() == ResourceType.Value.RESOURCE));
assertFalse(restDefinition.getResource().stream().anyMatch(r -> r.getType().getValueAsEnum() == ResourceType.Value.DOMAIN_RESOURCE));
Expand Down Expand Up @@ -125,9 +135,9 @@ public CapabilitiesChild() throws Exception {
}

@Override
public Response capabilities(String mode) {
public Response capabilities(String mode, String acceptHeaderValue) {
httpServletRequest = new MockHttpServletRequest();
return super.capabilities(mode);
return super.capabilities(mode, acceptHeaderValue);
}
}
}

0 comments on commit 7181721

Please sign in to comment.