diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/resources/Capabilities.java b/fhir-server/src/main/java/com/ibm/fhir/server/resources/Capabilities.java index 2cf0c37afd5..8e0478d5c22 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/resources/Capabilities.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/resources/Capabilities.java @@ -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; @@ -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; @@ -122,6 +124,23 @@ public class Capabilities extends FHIRResource { .map(rt -> ResourceType.Value.from(rt.getSimpleName())) .collect(Collectors.toList()); + private static final Set 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."; @@ -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(); @@ -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 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); @@ -180,11 +203,30 @@ 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": @@ -192,7 +234,7 @@ private Resource computeCapabilityStatement(String mode) { case "full": case "normative": default: - return buildCapabilityStatement(); + return buildCapabilityStatement(fhirVersion); } } catch (Exception e) { throw new RuntimeException(e); @@ -290,7 +332,7 @@ private List 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); @@ -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 resources = new ArrayList<>(); - List resourceTypes = getSupportedResourceTypes(rsrcsGroup); + List resourceTypes = getSupportedResourceTypes(rsrcsGroup, fhirVersion); for (ResourceType.Value resourceType : resourceTypes) { String resourceTypeName = resourceType.value(); @@ -634,18 +674,23 @@ private List convertStringList(List stri * @return a list of resource types to support * @throws Exception */ - private List getSupportedResourceTypes(PropertyGroup rsrcsGroup) throws Exception { + private List getSupportedResourceTypes(PropertyGroup rsrcsGroup, FHIRVersion fhirVersion) throws Exception { + final List 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 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; } diff --git a/fhir-server/src/test/java/com/ibm/fhir/server/test/CapabilitiesTest.java b/fhir-server/src/test/java/com/ibm/fhir/server/test/CapabilitiesTest.java index 8c049ffc6c8..8a811b9a250 100644 --- a/fhir-server/src/test/java/com/ibm/fhir/server/test/CapabilitiesTest.java +++ b/fhir-server/src/test/java/com/ibm/fhir/server/test/CapabilitiesTest.java @@ -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"); @@ -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"); @@ -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"); @@ -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)); @@ -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); } } }