Skip to content
3 changes: 3 additions & 0 deletions docs/src/pages/guides/FHIRServerUsersGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -2082,6 +2082,7 @@ This section contains reference information about each of the configuration prop
|`fhirServer/core/ifNoneMatchReturnsNotModified`|boolean|When If-None-Match is specified, overrides the standard return status "412 Precondition Failed" to be "304 Not Modified". Useful in transaction bundles for clients not wanting the bundle to fail when a conflict is found.|
|`fhirServer/core/capabilitiesUrl`|string|The URL that is embedded in the default Capabilities statement|
|`fhirServer/core/externalBaseUrl`|string|The base URL that is embedded in the Search bundle response, as of version 4.9.0. Note that the base URL must not include a path segment that matches any FHIR resource type name (case-sensitive). For example, "https://example.com" or "https://example.com/my/patient/api" are fine, but "https://example.com/my/Patient/api" is not.|
|`fhirServer/core/defaultFhirVersion`|string|The implicit value to use for the MIME-type fhirVersion parameter on incoming Accept and Content-Type headers when the client has not passed an explicit value.|
|`fhirServer/core/useImplicitTypeScopingForWholeSystemInteractions`|boolean|Whether to apply implicit resource type scoping for whole-system search and whole-system history interactions where no `_type` values were passed. Only set to false if you are certain that there are no instances of unsupported resource types in the database.|
|`fhirServer/validation/failFast`|boolean|Indicates whether validation should fail fast on create and update interactions|
|`fhirServer/term/capabilitiesUrl`|string|The URL that is embedded in the Terminology Capabilities statement using `mode=terminology`|
Expand Down Expand Up @@ -2266,6 +2267,7 @@ This section contains reference information about each of the configuration prop
|`fhirServer/core/capabilitiesUrl`|null|
|`fhirServer/core/externalBaseUrl`|null|
|`fhirServer/core/ifNoneMatchReturnsNotModified`|false|
|`fhirServer/core/defaultFhirVersion`|4.0|
|`fhirServer/core/useImplicitTypeScopingForWholeSystemInteractions`|true|
|`fhirServer/validation/failFast`|false|
|`fhirServer/term/capabilitiesUrl`|null|
Expand Down Expand Up @@ -2419,6 +2421,7 @@ must restart the server for that change to take effect.
|`fhirServer/core/maxPageIncludeCount`|Y|Y|
|`fhirServer/core/capabilitiesUrl`|Y|Y|
|`fhirServer/core/externalBaseUrl`|Y|Y|
|`fhirServer/core/defaultFhirVersion`|Y|Y|
|`fhirServer/core/useImplicitTypeScopingForWholeSystemInteractions`|Y|Y|
|`fhirServer/validation/failFast`|Y|Y|
|`fhirServer/term/cachingDisabled`|N|N|
Expand Down
50 changes: 19 additions & 31 deletions fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfigHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ibm.fhir.config.PropertyGroup.PropertyEntry;
import com.ibm.fhir.core.FHIRVersionParam;
import com.ibm.fhir.exception.FHIRException;

import jakarta.json.JsonValue;
Expand All @@ -23,7 +25,7 @@
*/
public class FHIRConfigHelper {
private static final Logger log = Logger.getLogger(FHIRConfigHelper.class.getName());

//Constants
public static final String SEARCH_PROPERTY_TYPE_INCLUDE = "_include";
public static final String SEARCH_PROPERTY_TYPE_REVINCLUDE = "_revinclude";
Expand Down Expand Up @@ -84,7 +86,7 @@ private static JsonValue getPropertyFromTenantOrDefault(String propertyName) {
if (result == null && !FHIRConfiguration.DEFAULT_TENANT_ID.equals(tenantId)) {
try {
if (propertyName.startsWith(FHIRConfiguration.PROPERTY_DATASOURCES)) {
// Issue #639. Prevent datasource lookups from falling back to
// Issue #639. Prevent datasource lookups from falling back to
// the default datasource which breaks tenant isolation.
result = null;
} else {
Expand Down Expand Up @@ -160,43 +162,29 @@ private static <T> T getTypedProperty(Class<T> expectedDataType, String property
}

return (result != null ? result : defaultValue);
}
}

/**
* This method returns the list of supported resource types
* @return a list of resource types that isn't null
* @return a non-null list of supported resource types for fhirVersion 4.3
*/
public static List<String> getSupportedResourceTypes() throws FHIRException {
List<String> result = new ArrayList<>();
return new ArrayList<>(getSupportedResourceTypes(FHIRVersionParam.VERSION_43));
}

PropertyGroup rsrcsGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
List<PropertyEntry> rsrcsEntries;
/**
* @return a non-null list of supported resource types for the given fhirVersion
*/
public static Set<String> getSupportedResourceTypes(FHIRVersionParam fhirVersion) throws FHIRException {
PropertyGroup resourcesGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
try {
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
// and skip the abstract types Resource and DomainResource
// It would be nice to be able to verify if the resource names were valid, but
// not possible at this layer of the code.
if (!FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(name) &&
!"Resource".equals(name) &&
!"DomainResource".equals(name)) {
result.add(name);
}
}
}
ResourcesConfigAdapter configAdapter = new ResourcesConfigAdapter(resourcesGroup, fhirVersion);
return configAdapter.getSupportedResourceTypes();
} catch (Exception e) {
log.fine("FHIRConfigHelper.getSupportedResourceTypes is configured with no "
+ "resources in the server config file or is not configured properly");
log.log(Level.SEVERE, "Unexpected exception while constructing a ResourcesConfigAdapter for fhirServer/resources", e);
throw new FHIRException(e);
}

return result;
}

/**
* Retrieves the search property restrictions.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class FHIRConfiguration {
public static final String PROPERTY_MAX_PAGE_SIZE = "fhirServer/core/maxPageSize";
public static final String PROPERTY_MAX_PAGE_INCLUDE_COUNT = "fhirServer/core/maxPageIncludeCount";
public static final String PROPERTY_CAPABILITIES_URL = "fhirServer/core/capabilitiesUrl";
public static final String PROPERTY_DEFAULT_FHIR_VERSION = "fhirServer/core/defaultFhirVersion";
// Migration properties
public static final String PROPERTY_WHOLE_SYSTEM_TYPE_SCOPING = "fhirServer/core/useImplicitTypeScopingForWholeSystemInteractions";

Expand Down
40 changes: 40 additions & 0 deletions fhir-config/src/main/java/com/ibm/fhir/config/Interaction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* (C) Copyright IBM Corp. 2022
*
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.fhir.config;

/**
* Interaction constants to the allowed values of the
* fhirServer/resources/[resourceType]/interactions config property
*/
public enum Interaction {
Comment thread
lmsurpre marked this conversation as resolved.
CREATE("create"),
DELETE("delete"),
HISTORY("history"),
PATCH("patch"),
READ("read"),
SEARCH("search"),
UPDATE("update"),
VREAD("vread");

private final String value;

Interaction(String value) {
this.value = value;
}

public String value() {
return value;
}

public static Interaction from(String value) {
for (Interaction interaction : Interaction.values()) {
if (interaction.value.equals(value)) {
return interaction;
}
}
throw new IllegalArgumentException(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* (C) Copyright IBM Corp. 2022
*
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.fhir.config;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ibm.fhir.config.PropertyGroup.PropertyEntry;
import com.ibm.fhir.core.FHIRVersionParam;
import com.ibm.fhir.core.util.ResourceTypeHelper;

/**
* An abstraction for the ibm-fhir-server fhirServer/resources property group
*/
public class ResourcesConfigAdapter {
public static final Logger log = Logger.getLogger(ResourcesConfigAdapter.class.getName());

private final Set<String> supportedTypes;
private final Map<Interaction, Set<String>> typesByInteraction;

/**
* Public constructor
*
* @param resourcesConfig a PropertyGroup instance for the fhirServer/resources property group
* @param fhirVersion a FHIRVersionParam with the fhirVersion to use for computing the applicable resource types
* @throws Exception
*/
public ResourcesConfigAdapter(PropertyGroup resourcesConfig, FHIRVersionParam fhirVersion) throws Exception {
Comment thread
lmsurpre marked this conversation as resolved.
supportedTypes = computeSupportedResourceTypes(resourcesConfig, fhirVersion);
typesByInteraction = computeTypesByInteraction(resourcesConfig);
}

/**
* @return an immutable, non-null set of supported resource types for the given fhirVersion
* @throws Exception
*/
public Set<String> getSupportedResourceTypes() {
return supportedTypes;
}

/**
* @return an immutable, non-null set of resource types that are configured for the given interaction and fhirVersion
*/
public Set<String> getSupportedResourceTypes(Interaction interaction) {
return typesByInteraction.get(interaction);
}

/**
* Construct the list of supported resource types from the passed configuration and fhirVersion
*
* @param resourcesConfig
* @param fhirVersion
* @return
* @throws Exception
*/
private Set<String> computeSupportedResourceTypes(PropertyGroup resourcesConfig, FHIRVersionParam fhirVersion) throws Exception {
Set<String> applicableTypes = ResourceTypeHelper.getResourceTypesFor(fhirVersion);

Set<String> result;
if (resourcesConfig == null || resourcesConfig.getBooleanProperty("open", true)) {
result = applicableTypes;
} else {
result = new LinkedHashSet<String>();
for (PropertyEntry rsrcsEntry : resourcesConfig.getProperties()) {
String name = rsrcsEntry.getName();

// Ensure we skip over the special property "open"
// and skip the abstract types Resource and DomainResource
if (FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(name) ||
ResourceTypeHelper.getAbstractResourceTypeNames().contains(name)) {
continue;
}

if (applicableTypes.contains(name)) {
result.add(name);
} else if (log.isLoggable(Level.FINE)) {
log.fine("Configured resource type '" + name + "' is not valid "
+ "or not applicable for fhirVersion " + fhirVersion.value());
}
}
}

return Collections.unmodifiableSet(result);
}

// note that this private method depends on the member supportedTypes having already been computed
private Map<Interaction, Set<String>> computeTypesByInteraction(PropertyGroup resourcesConfig) throws Exception {
Map<Interaction, Set<String>> typeMap = new HashMap<>();
if (resourcesConfig == null) {
for (Interaction interaction : Interaction.values()) {
typeMap.put(interaction, supportedTypes);
}
} else {
for (String resourceType : supportedTypes) {
List<String> interactions = resourcesConfig.getStringListProperty(resourceType + "/" + FHIRConfiguration.PROPERTY_FIELD_RESOURCES_INTERACTIONS);
if (interactions == null) {
interactions = resourcesConfig.getStringListProperty("Resource/" + FHIRConfiguration.PROPERTY_FIELD_RESOURCES_INTERACTIONS);
}

if (interactions == null) {
for (Interaction interaction : Interaction.values()) {
typeMap.computeIfAbsent(interaction, k -> new LinkedHashSet<>()).add(resourceType);
}
continue;
}

for (String interactionString : interactions) {
Interaction interaction = Interaction.from(interactionString);
typeMap.computeIfAbsent(interaction, k -> new LinkedHashSet<>()).add(resourceType);
}
}
}

Map<Interaction, Set<String>> finalMap = new HashMap<>();
for (Entry<Interaction, Set<String>> entry : typeMap.entrySet()) {
finalMap.put(entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
}
return Collections.unmodifiableMap(finalMap);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* (C) Copyright IBM Corp. 2022
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.config.test;

import static org.testng.Assert.assertEquals;

import java.util.Set;

import org.testng.annotations.Test;

import com.ibm.fhir.config.Interaction;
import com.ibm.fhir.config.PropertyGroup;
import com.ibm.fhir.config.ResourcesConfigAdapter;
import com.ibm.fhir.core.FHIRVersionParam;

import jakarta.json.Json;
import jakarta.json.JsonObject;

public class ResourcesConfigAdapterTest {
@Test
public void testGetSupportedResourceTypes_r4() throws Exception {
JsonObject json = Json.createObjectBuilder().build();
PropertyGroup pg = new PropertyGroup(json);
ResourcesConfigAdapter resourcesConfigAdapter = new ResourcesConfigAdapter(pg, FHIRVersionParam.VERSION_40);

Set<String> supportedResourceTypes = resourcesConfigAdapter.getSupportedResourceTypes();
assertEquals(supportedResourceTypes.size(), 125);

for (Interaction interaction : Interaction.values()) {
supportedResourceTypes = resourcesConfigAdapter.getSupportedResourceTypes(interaction);
assertEquals(supportedResourceTypes.size(), 125);
}
}

@Test
public void testGetSupportedResourceTypes_r4b() throws Exception {
JsonObject json = Json.createObjectBuilder().build();
PropertyGroup pg = new PropertyGroup(json);
ResourcesConfigAdapter resourcesConfigAdapter = new ResourcesConfigAdapter(pg, FHIRVersionParam.VERSION_43);

Set<String> supportedResourceTypes = resourcesConfigAdapter.getSupportedResourceTypes();
assertEquals(supportedResourceTypes.size(), 141);

for (Interaction interaction : Interaction.values()) {
supportedResourceTypes = resourcesConfigAdapter.getSupportedResourceTypes(interaction);
assertEquals(supportedResourceTypes.size(), 141);
}
}
}
Loading