From 11f750e5da992bf68152590efdf5849325e7cdd9 Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Wed, 12 Jan 2022 15:05:47 -0500 Subject: [PATCH] issues #3184 and #3186 - update Capabilities Signed-off-by: Lee Surprenant --- .../fhir/server/resources/Capabilities.java | 118 +++++++++++------- .../fhir/server/test/CapabilitiesTest.java | 18 +-- 2 files changed, 83 insertions(+), 53 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 76f95ae7ff0..2cf0c37afd5 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 @@ -37,6 +37,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; @@ -44,6 +45,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.CacheControl; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -52,7 +54,6 @@ import com.ibm.fhir.config.FHIRConfigHelper; import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.PropertyGroup; -import com.ibm.fhir.config.PropertyGroup.PropertyEntry; import com.ibm.fhir.core.FHIRMediaType; import com.ibm.fhir.exception.FHIROperationException; import com.ibm.fhir.model.format.Format; @@ -90,6 +91,7 @@ import com.ibm.fhir.model.type.code.RestfulCapabilityMode; import com.ibm.fhir.model.type.code.SystemRestfulInteraction; import com.ibm.fhir.model.type.code.TypeRestfulInteraction; +import com.ibm.fhir.model.type.code.TypeRestfulInteraction.Value; import com.ibm.fhir.model.util.ModelSupport; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; import com.ibm.fhir.registry.FHIRRegistry; @@ -126,6 +128,9 @@ public class Capabilities extends FHIRResource { private static final String CAPABILITY_STATEMENT_CACHE_NAME = "com.ibm.fhir.server.resources.Capabilities.statementCache"; + @Context + protected HttpServletRequest httpServletRequest; + // Constructor public Capabilities() throws Exception { super(); @@ -290,7 +295,8 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { PropertyGroup rsrcsGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES); // Build the list of interactions, searchIncludes, and searchRevIncludes supported for each resource type by default. - List defaultInteractions = buildInteractions(ALL_INTERACTIONS); + List systemInteractions = buildSystemInteractions(ALL_INTERACTIONS); + List defaultTypeInteractions = buildTypeInteractions(ALL_INTERACTIONS); List defaultSearchIncludes = Collections.emptyList(); List defaultSearchRevIncludes = Collections.emptyList(); if (rsrcsGroup != null) { @@ -298,7 +304,8 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { if (parentResourcePropGroup != null) { List interactionConfig = parentResourcePropGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_INTERACTIONS); if (interactionConfig != null) { - defaultInteractions = buildInteractions(interactionConfig); + systemInteractions = buildSystemInteractions(interactionConfig); + defaultTypeInteractions = buildTypeInteractions(interactionConfig); } List searchIncludeConfig = parentResourcePropGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_INCLUDES); if (searchIncludeConfig != null) { @@ -338,6 +345,8 @@ 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<>(); @@ -375,7 +384,7 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { } // Build the list of interactions, searchIncludes, and searchRevIncludes supported for the resource type. - List interactions = defaultInteractions; + List interactions = defaultTypeInteractions; List searchIncludes = defaultSearchIncludes; List searchRevIncludes = defaultSearchRevIncludes; if (rsrcsGroup != null) { @@ -384,7 +393,7 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { List resourceInteractionConfig = resourcePropGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_INTERACTIONS); if (resourceInteractionConfig != null) { - interactions = buildInteractions(resourceInteractionConfig); + interactions = buildTypeInteractions(resourceInteractionConfig); } List searchIncludeConfig = resourcePropGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_INCLUDES); if (searchIncludeConfig != null) { @@ -399,7 +408,7 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { } // Build the ConformanceResource for this resource type. - Rest.Resource cr = Rest.Resource.builder() + Rest.Resource.Builder crb = Rest.Resource.builder() .type(ResourceType.of(resourceType)) .profile(Canonical.of("http://hl7.org/fhir/profiles/" + resourceTypeName)) .interaction(interactions) @@ -412,20 +421,12 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { .conditionalRead(ConditionalReadStatus.FULL_SUPPORT) .searchParam(conformanceSearchParams) .searchInclude(searchIncludes) - .searchRevInclude(searchRevIncludes) - .build(); - - resources.add(cr); - } - - // Determine if transactions are supported for this FHIR Server configuration. - SystemRestfulInteraction transactionMode = SystemRestfulInteraction.BATCH; - try { - boolean txnSupported = getPersistenceImpl().isTransactional(); - transactionMode = (txnSupported ? SystemRestfulInteraction.TRANSACTION - : SystemRestfulInteraction.BATCH); - } catch (Throwable t) { - log.log(Level.WARNING, "Unexpected error while reading server transaction mode setting", t); + .searchRevInclude(searchRevIncludes); + // Set readHistory to true if vread is supported for this resource type; otherwise leave it null + if (interactions.stream().anyMatch(i -> i.getCode().getValueAsEnum() == Value.VREAD)) { + crb.readHistory(true); + } + resources.add(crb.build()); } CapabilityStatement.Rest.Security.Builder securityBuilder = CapabilityStatement.Rest.Security.builder() @@ -489,9 +490,7 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { .mode(RestfulCapabilityMode.SERVER) .security(securityBuilder.build()) .resource(addSupportedProfilesToResources(resources)) - .interaction(CapabilityStatement.Rest.Interaction.builder() - .code(transactionMode) - .build()) + .interaction(systemInteractions) .operation(mapOperationDefinitionsToRestOperations(systemOps)) .build(); @@ -529,7 +528,7 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { .status(PublicationStatus.ACTIVE) .date(DateTime.now(ZoneOffset.UTC)) .kind(CapabilityStatementKind.INSTANCE) - .fhirVersion(FHIRVersion.VERSION_4_3_0_CIBUILD) + .fhirVersion(fhirVersion) .format(format) .patchFormat(Code.of(FHIRMediaType.APPLICATION_JSON_PATCH), Code.of(FHIRMediaType.APPLICATION_FHIR_JSON), @@ -558,28 +557,61 @@ private CapabilityStatement buildCapabilityStatement() throws Exception { return conformance; } + /** + * @param interactionConfig a list of strings that represent the RESTful interactions to support at the system level + * (create, read, vread, update, patch, delete, history, and/or search) + * @return a list of Rest.Resource.Interaction objects to include in the CapabilityStatement + * @throws FHIRPersistenceException + */ + private List buildSystemInteractions(List interactionConfig) throws Exception { + List interactions = new ArrayList<>(); + interactions.add(buildSystemInteractionStatement(SystemRestfulInteraction.BATCH)); + try { + // If transactions are supported for this FHIR Server configuration + if (getPersistenceImpl().isTransactional()) { + interactions.add(buildSystemInteractionStatement(SystemRestfulInteraction.TRANSACTION)); + } + } catch (Throwable t) { + log.log(Level.WARNING, "Unexpected error while reading server transaction mode setting", t); + } + + if (interactionConfig != null) { + for (String interactionString : interactionConfig) { + if ("search".equals(interactionString)) { + // special case for search since the value set uses "search-system" instead of just "search" + interactions.add(buildSystemInteractionStatement(SystemRestfulInteraction.SEARCH_SYSTEM)); + } else if ("history".equals(interactionString)){ + // special case for search since the value set uses "history-system" instead of just "history" + interactions.add(buildSystemInteractionStatement(SystemRestfulInteraction.HISTORY_SYSTEM)); + } + } + } + return interactions; + } + /** * @param interactionConfig a list of strings that represent the RESTful interactions to support for this resource type * (create, read, vread, update, patch, delete, history, and/or search) * @return a list of Rest.Resource.Interaction objects to include in the CapabilityStatement * @throws FHIRPersistenceException */ - private List buildInteractions(List interactionConfig) throws Exception { + private List buildTypeInteractions(List interactionConfig) throws Exception { if (interactionConfig == null) return null; List interactions = new ArrayList<>(); for (String interactionString : interactionConfig) { if ("search".equals(interactionString)) { // special case for search since the value set uses "search-type" instead of just "search" - interactions.add(buildInteractionStatement(TypeRestfulInteraction.SEARCH_TYPE)); + interactions.add(buildTypeInteractionStatement(TypeRestfulInteraction.SEARCH_TYPE)); } else if ("history".equals(interactionString)){ - // special case for search since the value set uses "history-instance" instead of just "history" - interactions.add(buildInteractionStatement(TypeRestfulInteraction.HISTORY_INSTANCE)); + // special case for search since the value set has "history-type" and "history-instance" instead of just "history" + interactions.add(buildTypeInteractionStatement(TypeRestfulInteraction.HISTORY_TYPE)); + interactions.add(buildTypeInteractionStatement(TypeRestfulInteraction.HISTORY_INSTANCE)); } else if ("delete".equals(interactionString)) { // special case for delete since we shouldn't advertise it if the PL doesn't support it - interactions.add(buildInteractionStatement(TypeRestfulInteraction.DELETE)); + interactions.add(buildTypeInteractionStatement(TypeRestfulInteraction.DELETE)); } else { - interactions.add(buildInteractionStatement(TypeRestfulInteraction.of(interactionString))); + interactions.add(buildTypeInteractionStatement(TypeRestfulInteraction.of(interactionString))); } } return interactions; @@ -611,20 +643,9 @@ private List getSupportedResourceTypes(PropertyGroup rsrcsGr if (rsrcsGroup.getBooleanProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN, true)) { resourceTypes = ALL_RESOURCE_TYPES; } else { - List rsrcsEntries = rsrcsGroup.getProperties(); - if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) { - for (PropertyEntry rsrcsEntry : rsrcsEntries) { - String name = rsrcsEntry.getName(); - // Ensure we skip over the special property "open" and process only the others - if (!FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(name)) { - // Skip the abstract types Resource and DomainResource - if (!Resource.class.equals(ModelSupport.getResourceType(name)) && - !DomainResource.class.equals(ModelSupport.getResourceType(name))) { - resourceTypes.add(ResourceType.Value.from(name)); - } - } - } - } + resourceTypes = FHIRConfigHelper.getSupportedResourceTypes().stream() + .map(ResourceType.Value::from) + .collect(Collectors.toList()); } return resourceTypes; } @@ -799,8 +820,13 @@ private String getNotificationResourceTypes() throws Exception { return Arrays.asList(notificationResourceTypes).toString().replace("[", "").replace("]", "").replace(" ", ""); } - private Interaction buildInteractionStatement(TypeRestfulInteraction value) { - Interaction ci = Interaction.builder().code(value).build(); + private Rest.Resource.Interaction buildTypeInteractionStatement(TypeRestfulInteraction value) { + Rest.Resource.Interaction ci = Rest.Resource.Interaction.builder().code(value).build(); + return ci; + } + + private Rest.Interaction buildSystemInteractionStatement(SystemRestfulInteraction value) { + Rest.Interaction ci = Rest.Interaction.builder().code(value).build(); return ci; } } 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 c1f50feee89..8c049ffc6c8 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,6 +27,7 @@ import com.ibm.fhir.server.resources.Capabilities; public class CapabilitiesTest { + private static final boolean DEBUG = false; @BeforeClass void setup() { @@ -51,7 +52,7 @@ void testBuildCapabilityStatement_resources_omitted() throws Exception { assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements"); CapabilityStatement.Rest restDefinition = capabilityStatement.getRest().get(0); - assertRestDefinition(restDefinition, 141, 8, 0, 0, 8, 0, 0); + assertRestDefinition(restDefinition, 4, 141, 9, 0, 0, 9, 0, 0); } @Test @@ -66,7 +67,7 @@ void testBuildCapabilityStatement_resources_empty() throws Exception { assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements"); CapabilityStatement.Rest restDefinition = capabilityStatement.getRest().get(0); - assertRestDefinition(restDefinition, 141, 0, 0, 0, 0, 0, 0); + assertRestDefinition(restDefinition, 0, 141, 0, 0, 0, 0, 0, 0); } @Test @@ -81,12 +82,15 @@ void testBuildCapabilityStatement_resources_filtered() throws Exception { assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements"); CapabilityStatement.Rest restDefinition = capabilityStatement.getRest().get(0); - assertRestDefinition(restDefinition, 2, 2, 1, 0, 4, 0, 1); + assertRestDefinition(restDefinition, 0, 2, 2, 1, 0, 5, 0, 1); } - private void assertRestDefinition(CapabilityStatement.Rest restDefinition, int numOfResources, - int patientInteractions, int patientIncludes, int patientRevIncludes, - int practitionerInteractions, int practitionerIncludes, int practitionerRevIncludes) { + 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)); @@ -96,7 +100,7 @@ private void assertRestDefinition(CapabilityStatement.Rest restDefinition, int n } private void assertResourceDefinition(CapabilityStatement.Rest restDefinition, ResourceType.Value resourceType, int numOfInteractions, - int numIncludes, int numRevIncludes) { + int numIncludes, int numRevIncludes) { Optional resource = restDefinition.getResource().stream() .filter(r -> r.getType().getValueAsEnum() == resourceType) .findFirst();