From b91f34aa4b555111ef48f9f780143fb4613c2c9c Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Wed, 12 Jan 2022 17:55:59 -0500 Subject: [PATCH] issue #3192 - fhirVersion-aware CapabilityStatement Signed-off-by: Lee Surprenant --- .../fhir/server/resources/Capabilities.java | 77 +++++++++++++++++-- .../fhir/server/test/CapabilitiesTest.java | 32 +++++--- 2 files changed, 92 insertions(+), 17 deletions(-) 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 9a9f9b86870..774a2691bd4 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; @@ -121,6 +123,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."; @@ -137,7 +156,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(); @@ -147,13 +166,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); @@ -179,11 +202,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": @@ -191,7 +233,7 @@ private Resource computeCapabilityStatement(String mode) { case "full": case "normative": default: - return buildCapabilityStatement(); + return buildCapabilityStatement(fhirVersion); } } catch (Exception e) { throw new RuntimeException(e); @@ -289,7 +331,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); @@ -353,13 +395,17 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { } com.ibm.fhir.model.type.Boolean isUpdateCreate = com.ibm.fhir.model.type.Boolean.of(isUpdateCreateEnabled()); +<<<<<<< HEAD FHIRVersion fhirVersion = FHIRVersion.VERSION_4_0_1; +======= + +>>>>>>> 2b4dfe67da (issue #3192 - fhirVersion-aware CapabilityStatement) // 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(); @@ -661,11 +707,22 @@ 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())); + } } +<<<<<<< HEAD List resourceTypes = new ArrayList<>(); if (rsrcsGroup.getBooleanProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN, true)) { resourceTypes = ALL_RESOURCE_TYPES; @@ -678,6 +735,10 @@ private List getSupportedResourceTypes(PropertyGroup rsrcsGr .map(ResourceType.Value::from) .collect(Collectors.toList()); >>>>>>> c9cadcb75b (issues #3184 and #3186 - update Capabilities) +======= + if (fhirVersion.getValueAsEnum() == FHIRVersion.Value.VERSION_4_0_1) { + resourceTypes.removeAll(R4B_ONLY_RESOURCES); +>>>>>>> 2b4dfe67da (issue #3192 - fhirVersion-aware CapabilityStatement) } 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 62f9168a7c8..ea6078c3bf3 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 @@ -28,8 +28,6 @@ import com.ibm.fhir.server.resources.Capabilities; public class CapabilitiesTest { - private static final boolean DEBUG = false; - SearchHelper searchHelper = new SearchHelper(); @BeforeClass @@ -49,22 +47,38 @@ void testBuildCapabilityStatement_resources_omitted() throws Exception { FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata"); CapabilitiesChild c = new CapabilitiesChild(searchHelper); - 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, 141, 9, 1, 1, 9, 1, 1); + assertRestDefinition(restDefinition, 4, 128, 9, 1, 1, 9, 1, 1); } @Test - void testBuildCapabilityStatement_resources_empty() throws Exception { + void testBuildCapabilityStatement_resources_empty_r4() throws Exception { FHIRRequestContext.get().setTenantId("empty"); FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata"); CapabilitiesChild c = new CapabilitiesChild(searchHelper); - Response capabilities = c.capabilities("full"); + 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); + + // batch and transaction + assertRestDefinition(restDefinition, 2, 128, 0, 0, 0, 0, 0, 0); + } + + @Test + 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", "application/fhir+json;fhirVersion=4.3"); CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class); assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements"); @@ -79,7 +93,7 @@ void testBuildCapabilityStatement_resources_filtered() throws Exception { FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata"); CapabilitiesChild c = new CapabilitiesChild(searchHelper); - 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"); @@ -130,9 +144,9 @@ public CapabilitiesChild(SearchHelper searchHelper) 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); } @Override