Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issues 3242 and 3265 - scope search-system and search-history for fhirVersion #3268

Merged
merged 8 commits into from
Feb 4, 2022
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 {
lmsurpre marked this conversation as resolved.
Show resolved Hide 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 {
lmsurpre marked this conversation as resolved.
Show resolved Hide 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