From f253a8f22b1e999bdefb72bf52a4f84d8eb771be Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Thu, 4 Nov 2021 21:33:50 -0400 Subject: [PATCH] issues #1625 and #1596 - apply search parameter This is a big, end-user-visible change to the way we load search parameters and apply filtering from fhir-server-config. Previously, we loaded the built-in search parameters from the registry, but then applied tenant-specific extension-search-parameters on top of that. Now, we have a new ExtensionSearchParametersResourceProvider that reads the same tenant-specific extension-search-parameters (for backwards compatibility) and provides those resources to the registry in a tenant-aware manner. This is #1596. Additionally, we used to apply search filtering on each and every request. Now, the ParametersUtil will loop through all configured tenants and load their search parameters from the registry during initialization. This tenant-aware search parameter map will then be consulted anytime we need a search parameter or set of search parameters in the context of a particular request (for a particular tenant). Signed-off-by: Lee Surprenant --- .../ibm/fhir/config/FHIRConfiguration.java | 42 +- .../config/test/FHIRConfigHelperTest.java | 3 +- .../com/ibm/fhir/registry/FHIRRegistry.java | 2 +- .../resource/FHIRRegistryResource.java | 3 + .../spi/AbstractRegistryResourceProvider.java | 7 + .../spi/FHIRRegistryResourceProvider.java | 8 +- .../util/PackageRegistryResourceProvider.java | 6 +- .../com/ibm/fhir/search/SearchConstants.java | 24 +- ...nsionSearchParametersResourceProvider.java | 117 +++++ .../fhir/search/parameters/ParametersMap.java | 8 +- .../search/parameters/ParametersUtil.java | 176 +++++-- .../TenantSpecificSearchParameterCache.java | 33 +- .../com/ibm/fhir/search/util/SearchUtil.java | 470 ++---------------- ....registry.spi.FHIRRegistryResourceProvider | 1 + .../compartment/CompartmentUtilTest.java | 2 +- .../MultiTenantSearchParameterTest.java | 158 +----- .../parameters/ParametersSearchUtilTest.java | 40 +- .../search/parameters/ParametersUtilTest.java | 17 +- .../ibm/fhir/search/test/BaseSearchTest.java | 15 +- .../test/SampleRegistryResourceProvider.java | 4 +- .../com/ibm/fhir/search/test/mains/Main.java | 4 +- .../resources/logging.unitTest.properties | 51 ++ 22 files changed, 458 insertions(+), 733 deletions(-) create mode 100644 fhir-search/src/main/java/com/ibm/fhir/search/parameters/ExtensionSearchParametersResourceProvider.java create mode 100644 fhir-search/src/main/resources/META-INF/services/com.ibm.fhir.registry.spi.FHIRRegistryResourceProvider create mode 100644 fhir-search/src/test/resources/logging.unitTest.properties diff --git a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java index 1cb2ebe917f..a17b725758a 100644 --- a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java +++ b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java @@ -246,38 +246,26 @@ public void clearConfiguration() { } /** - * This method returns the list of tenant id's for which a configuration exists. + * This method returns the list of tenant id's for which a configuration directory exists. * @return */ public List getConfiguredTenants() { - log.entering(this.getClass().getName(), "getConfiguredTenants"); - - try { - List result = new ArrayList<>(); - - // 'configDir' represents the directory that contains the tenant ids - // Example: "/opt/ibm/fhir-server/wlp/usr/servers/fhir-server/config". - File configDir = new File(getConfigHome() + CONFIG_LOCATION); - log.fine("Listing tenant id's rooted at directory: " + configDir.getName()); - - // List the directories within 'configDir' that contain a fhir-server-config.json file. - for (File f : configDir.listFiles()) { - // For a directory, let's verify that a config exists within it. - // If yes, then add the name of the directory to the result list, as that - // represents a tenant id. - if (f.isDirectory()) { - File configFile = new File(f, CONFIG_FILE_BASENAME); - if (configFile.exists() && configFile.isFile()) { - result.add(f.getName()); - } - } - } + List result = new ArrayList<>(); - log.fine("Returning list of tenant ids: " + result.toString()); + // 'configDir' represents the directory that contains the tenant ids + // Example: "/opt/ibm/fhir-server/wlp/usr/servers/fhir-server/config". + File configDir = new File(getConfigHome() + CONFIG_LOCATION); + log.fine("Listing tenant id's rooted at directory: " + configDir.getName()); - return result; - } finally { - log.exiting(this.getClass().getName(), "getConfiguredTenants"); + // List the directories within 'configDir' that contain a fhir-server-config.json file. + for (File f : configDir.listFiles()) { + if (f.isDirectory()) { + result.add(f.getName()); + } } + + log.fine("Returning list of tenant ids: " + result.toString()); + + return result; } } diff --git a/fhir-config/src/test/java/com/ibm/fhir/config/test/FHIRConfigHelperTest.java b/fhir-config/src/test/java/com/ibm/fhir/config/test/FHIRConfigHelperTest.java index 950f2099f7e..73b76208fbb 100644 --- a/fhir-config/src/test/java/com/ibm/fhir/config/test/FHIRConfigHelperTest.java +++ b/fhir-config/src/test/java/com/ibm/fhir/config/test/FHIRConfigHelperTest.java @@ -336,11 +336,12 @@ public void testTenant5() throws Exception { public void testGetConfiguredTenants() { List tenants = FHIRConfiguration.getInstance().getConfiguredTenants(); assertNotNull(tenants); - assertEquals(5, tenants.size()); + assertEquals(6, tenants.size()); assertTrue(tenants.contains("default")); assertTrue(tenants.contains("tenant1")); assertTrue(tenants.contains("tenant2")); assertTrue(tenants.contains("tenant3")); + assertTrue(tenants.contains("tenant4")); assertTrue(tenants.contains("tenant5")); } diff --git a/fhir-registry/src/main/java/com/ibm/fhir/registry/FHIRRegistry.java b/fhir-registry/src/main/java/com/ibm/fhir/registry/FHIRRegistry.java index 2967ff948ce..3a851627db0 100644 --- a/fhir-registry/src/main/java/com/ibm/fhir/registry/FHIRRegistry.java +++ b/fhir-registry/src/main/java/com/ibm/fhir/registry/FHIRRegistry.java @@ -130,7 +130,7 @@ public Collection getProfiles(String type) { * Get the resource for the given canonical url and resource type * * @param url - * the canonical url + * the canonical url (with optional version postfix) * @param resourceType * the resource type * @return diff --git a/fhir-registry/src/main/java/com/ibm/fhir/registry/resource/FHIRRegistryResource.java b/fhir-registry/src/main/java/com/ibm/fhir/registry/resource/FHIRRegistryResource.java index eadf8350fde..07269344328 100644 --- a/fhir-registry/src/main/java/com/ibm/fhir/registry/resource/FHIRRegistryResource.java +++ b/fhir-registry/src/main/java/com/ibm/fhir/registry/resource/FHIRRegistryResource.java @@ -235,6 +235,9 @@ public int hashCode() { } } + /** + * @implNote returns the exact string in the version element of the corresponding resource + */ @Override public String toString() { return version; diff --git a/fhir-registry/src/main/java/com/ibm/fhir/registry/spi/AbstractRegistryResourceProvider.java b/fhir-registry/src/main/java/com/ibm/fhir/registry/spi/AbstractRegistryResourceProvider.java index a38e50c6054..9c0185969de 100644 --- a/fhir-registry/src/main/java/com/ibm/fhir/registry/spi/AbstractRegistryResourceProvider.java +++ b/fhir-registry/src/main/java/com/ibm/fhir/registry/spi/AbstractRegistryResourceProvider.java @@ -45,6 +45,13 @@ public final FHIRRegistryResource getRegistryResource(Class return null; } + /** + * Return a sorted list of FHIRRegistryResource with the passed canonical url + * + * @param resourceType + * @param url the canonical url for this resource (without version suffix) + * @return a list of FHIRRegistryResources with this url, sorted from low to high by version + */ protected abstract List getRegistryResources(Class resourceType, String url); @Override diff --git a/fhir-registry/src/main/java/com/ibm/fhir/registry/spi/FHIRRegistryResourceProvider.java b/fhir-registry/src/main/java/com/ibm/fhir/registry/spi/FHIRRegistryResourceProvider.java index ecef145ed7a..1f83ce4f7ec 100644 --- a/fhir-registry/src/main/java/com/ibm/fhir/registry/spi/FHIRRegistryResourceProvider.java +++ b/fhir-registry/src/main/java/com/ibm/fhir/registry/spi/FHIRRegistryResourceProvider.java @@ -38,7 +38,7 @@ public interface FHIRRegistryResourceProvider { * @param resourceType * the resource type of the registry resource * @return - * the registry resources from this provider for the given resource type + * the registry resources from this provider for the given resource type; never null */ Collection getRegistryResources(Class resourceType); @@ -46,7 +46,7 @@ public interface FHIRRegistryResourceProvider { * Get all the registry resources from this provider * * @return - * all of the registry resources from this provider + * all of the registry resources from this provider; never null */ Collection getRegistryResources(); @@ -56,7 +56,7 @@ public interface FHIRRegistryResourceProvider { * @param type * the constrained resource type * @return - * the profile resources from this provider that constrain the given resource type + * the profile resources from this provider that constrain the given resource type; never null */ Collection getProfileResources(String type); @@ -67,7 +67,7 @@ public interface FHIRRegistryResourceProvider { * @param type * the search parameter type * @return - * the search parameter resources from this provider with the given search parameter type + * the search parameter resources from this provider with the given search parameter type; never null */ Collection getSearchParameterResources(String type); diff --git a/fhir-registry/src/main/java/com/ibm/fhir/registry/util/PackageRegistryResourceProvider.java b/fhir-registry/src/main/java/com/ibm/fhir/registry/util/PackageRegistryResourceProvider.java index 6865fb90fd1..3ad39d7baf2 100644 --- a/fhir-registry/src/main/java/com/ibm/fhir/registry/util/PackageRegistryResourceProvider.java +++ b/fhir-registry/src/main/java/com/ibm/fhir/registry/util/PackageRegistryResourceProvider.java @@ -52,7 +52,8 @@ public PackageRegistryResourceProvider() { @Override protected List getRegistryResources(Class resourceType, String url) { - return registryResourceMap.getOrDefault(resourceType, Collections.emptyMap()).getOrDefault(url, Collections.emptyList()); + return registryResourceMap.getOrDefault(resourceType, Collections.emptyMap()) + .getOrDefault(url, Collections.emptyList()); } @Override @@ -93,6 +94,9 @@ public Collection getSearchParameterResources(String type) .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); } + /** + * @implNote the list of FHIRRegistryResource is sorted from low to high on version + */ private Map, Map>> buildRegistryResourceMap() { Map, Map>> registryResourceMap = new HashMap<>(); for (FHIRRegistryResource registryResource : registryResources) { diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java b/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java index 47df6626ee8..b0f51c6cfbf 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java @@ -41,11 +41,6 @@ private SearchConstants() { public static final String LOG_BOUNDARY = "---------------------------------------------------------"; - // XML Processing. - public static final String DTM_MANAGER = "com.sun.org.apache.xml.internal.dtm.DTMManager"; - - public static final String DTM_MANAGER_DEFAULT = "com.sun.org.apache.xml.internal.dtm.ref.DTMManagerDefault"; - // Line Separator public static final String NL = System.getProperty("line.separator"); @@ -53,13 +48,6 @@ private SearchConstants() { // @see https://www.hl7.org/fhir/r4/search.html#escaping public static final String BACKSLASH_NEGATIVE_LOOKBEHIND = "(? SYSTEM_LEVEL_GLOBAL_PARAMETER_NAMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(ID, LAST_UPDATED, PROFILE, SECURITY, SOURCE, TAG))); - + // Empty Query String public static final String EMPTY_QUERY_STRING = ""; diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ExtensionSearchParametersResourceProvider.java b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ExtensionSearchParametersResourceProvider.java new file mode 100644 index 00000000000..2d2830dea80 --- /dev/null +++ b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ExtensionSearchParametersResourceProvider.java @@ -0,0 +1,117 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.fhir.search.parameters; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import com.ibm.fhir.config.FHIRConfiguration; +import com.ibm.fhir.config.FHIRRequestContext; +import com.ibm.fhir.model.resource.Resource; +import com.ibm.fhir.model.resource.SearchParameter; +import com.ibm.fhir.registry.resource.FHIRRegistryResource; +import com.ibm.fhir.registry.spi.AbstractRegistryResourceProvider; +import com.ibm.fhir.search.parameters.cache.TenantSpecificSearchParameterCache; + +/** + * A FHIRRegistryResourceProvider that provides SearchParameter resources from tenant configuration files + * (extension-search-parameters.json by default). + */ +public class ExtensionSearchParametersResourceProvider extends AbstractRegistryResourceProvider { + private static final Logger log = Logger.getLogger(ExtensionSearchParametersResourceProvider.class.getName()); + + private static final String NO_TENANT_SP_MAP_LOGGING = + "No tenant-specific search parameters found for tenant '%s'; trying %s "; + + /* + * This is our in-memory cache of SearchParameter objects. The cache is organized at the top level by tenant-id. + * SearchParameters contained in the default tenant's extension-search-parameters.json file are stored under the + * "default" tenant-id, and other tenants' SearchParameters (defined in their tenant-specific + * extension-search-parameters.json files) will be stored under their respective tenant-ids as well. The objects + * stored in our cache are of type CachedObjectHolder, with each one containing a List. + */ + private static TenantSpecificSearchParameterCache searchParameterCache = new TenantSpecificSearchParameterCache(); + + @Override + protected List getRegistryResources(Class resourceType, String url) { + if (resourceType != SearchParameter.class || url == null) { + return Collections.emptyList(); + } + + String tenantId = FHIRRequestContext.get().getTenantId(); + return getTenantOrDefaultExtensionSPs(tenantId).stream() + .filter(sp -> url.equals(sp.getUrl().getValue())) + .map(sp -> FHIRRegistryResource.from(sp)) + .sorted() + .collect(Collectors.toList()); + } + + @Override + public Collection getRegistryResources() { + String tenantId = FHIRRequestContext.get().getTenantId(); + return getTenantOrDefaultExtensionSPs(tenantId).stream() + .map(sp -> FHIRRegistryResource.from(sp)) + .collect(Collectors.toList()); + } + + @Override + public Collection getRegistryResources(Class resourceType) { + if (resourceType != SearchParameter.class) { + return Collections.emptyList(); + } + + return getRegistryResources(); + } + + @Override + public Collection getProfileResources(String type) { + return Collections.emptySet(); + } + + @Override + public Collection getSearchParameterResources(String type) { + Objects.requireNonNull(type); + + return getRegistryResources().stream() + .filter(rr -> type.equals(rr.getType())) + .collect(Collectors.toList()); + } + + /** + * Returns the extended search parameters for the specified tenant, + * the default tenant (if no tenant-specific extension search parameter file exists), + * or an empty list (if there are no extended search parameters or we hit an error while trying to get them). + * + * @param tenantId + * the tenant whose SearchParameters should be returned. + * @return a list of SearchParameter objects + */ + private static List getTenantOrDefaultExtensionSPs(String tenantId) { + List cachedObjectForTenant = null; + + try { + cachedObjectForTenant = searchParameterCache.getCachedObjectForTenant(tenantId); + + if (cachedObjectForTenant == null) { + + if (log.isLoggable(Level.FINE)) { + log.fine(String.format(NO_TENANT_SP_MAP_LOGGING, tenantId, FHIRConfiguration.DEFAULT_TENANT_ID)); + } + + cachedObjectForTenant = searchParameterCache.getCachedObjectForTenant(FHIRConfiguration.DEFAULT_TENANT_ID); + } + } catch (Exception e) { + log.log(Level.WARNING, "Error while loading extension search parameters for tenant " + tenantId, e); + } + + return cachedObjectForTenant == null ? Collections.emptyList() : cachedObjectForTenant; + } +} diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ParametersMap.java b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ParametersMap.java index d748648b60b..da09117f36a 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ParametersMap.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ParametersMap.java @@ -48,6 +48,9 @@ public void insert(String code, SearchParameter parameter) { Set previousParams = codeMap.get(code); if (previousParams != null && previousParams.size() > 0) { if (log.isLoggable(Level.FINE)) { + if (parameter.getVersion() != null && parameter.getVersion().hasValue()) { + + } log.fine("SearchParameter with code '" + code + "' already exists; adding additional parameter '" + url + "'"); } } @@ -63,9 +66,10 @@ public void insert(String code, SearchParameter parameter) { + "adding additional code '" + code + "'"); } } else { + String thatVersion = previous.getVersion() == null ? null : previous.getVersion().getValue(); log.info("SearchParameter '" + url + "' already exists with a different expression;\n" - + "replacing [id=" + previous.getId() + ", version=" + previous.getVersion().getValue() + ", expression=" + previous.getExpression().getValue() - + "] with [id=" + parameter.getId() + ", version=" + parameter.getVersion().getValue() + ", expression=" + parameter.getExpression().getValue() + "]"); + + "replacing [id=" + previous.getId() + ", version=" + thatVersion + ", expression=" + previous.getExpression().getValue() + + "] with [id=" + parameter.getId() + ", version=" + version + ", expression=" + parameter.getExpression().getValue() + "]"); } } canonicalMap.put(url, parameter); diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ParametersUtil.java b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ParametersUtil.java index 4fe19e6f7af..8009efaf5ce 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ParametersUtil.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/ParametersUtil.java @@ -7,21 +7,29 @@ package com.ibm.fhir.search.parameters; import java.io.PrintStream; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; +import com.ibm.fhir.config.FHIRConfiguration; +import com.ibm.fhir.config.FHIRRequestContext; +import com.ibm.fhir.config.PropertyGroup; +import com.ibm.fhir.config.PropertyGroup.PropertyEntry; import com.ibm.fhir.model.resource.Bundle; import com.ibm.fhir.model.resource.SearchParameter; import com.ibm.fhir.model.type.code.ResourceType; import com.ibm.fhir.model.type.code.SearchParamType; +import com.ibm.fhir.model.util.ModelSupport; import com.ibm.fhir.registry.FHIRRegistry; import com.ibm.fhir.search.SearchConstants; +import com.ibm.fhir.search.exception.SearchExceptionUtil; /** * Refactored the PopulateSearchParameterMap code, and marked class as final so there are no 'children' and inheritance @@ -40,6 +48,10 @@ public final class ParametersUtil { public static final String FHIR_DEFAULT_SEARCH_PARAMETERS_FILE = "search-parameters.json"; public static final String FROM_STEAM = "from_stream"; + private static final Set ALL_RESOURCE_TYPES = ModelSupport.getResourceTypes(true).stream() + .map(t -> ModelSupport.getTypeName(t)) + .collect(Collectors.toSet()); + // Exceptions: public static final String ERROR_EXCEPTION = "Error condition reading the FHIR Bundle of Search Parameters -> %s "; public static final String BUILTIN_ERROR_EXCEPTION = String.format(ERROR_EXCEPTION, FHIR_DEFAULT_SEARCH_PARAMETERS_FILE); @@ -58,7 +70,7 @@ public final class ParametersUtil { private static final String COMMA = ","; private static final String EQUALS = "="; - private static final Map builtInSearchParameters = loadBuiltIns(); + private static final Map> searchParameters = loadBuiltIns(); private static final String MISSING_EXPRESSION = "/NONE/"; @@ -75,65 +87,136 @@ public static void init() { } /** - * Loads the built-in search parameters and constructs . + * Loads the built-in search parameters and constructs. * - * @return a map of ParametersMaps, keyed by resourceType + * @return a tenant-aware map of maps from resourceType to ParametersMaps */ - private static Map loadBuiltIns() { + private static Map> loadBuiltIns() { try { - return buildSearchParametersMap(); + return Collections.unmodifiableMap(buildSearchParametersMap()); } catch (Exception e) { - log.log(Level.SEVERE, "Unexpected error while loading built-in search parameters", e); + log.log(Level.SEVERE, "Unexpected error while loading search parameters", e); } return Collections.emptyMap(); } - private static Map buildSearchParametersMap() { - Map typeToParamMap = new HashMap<>(); + private static Map> buildSearchParametersMap() throws Exception { + HashMap> result = new HashMap<>(); - for (SearchParameter parameter : getSearchParameters()) { - // Conditional Logging intentionally avoids forming of the String. + FHIRConfiguration config = FHIRConfiguration.getInstance(); + + for (String tenant : config.getConfiguredTenants()) { + // Ensure we get the extension search parameters for the proper tenant + FHIRRequestContext.get().setTenantId(tenant); if (log.isLoggable(Level.FINE)) { - log.fine(String.format(LOG_PARAMETERS, parameter.getCode().getValue())); + log.fine(("Computing search params for tenant " + tenant)); } - if (parameter.getExpression() == null || !parameter.getExpression().hasValue()) { - if (log.isLoggable(Level.FINE)) { - log.fine(String.format(MISSING_EXPRESSION_WARNING, parameter.getCode().getValue())); - } - } else { - /* - * In R4, SearchParameter changes from a single Base resource to an array. - * As Base is an array, there are going to be potential collisions in the map. - */ - List types = parameter.getBase(); - for (ResourceType type : types) { - String base = type.getValue(); + PropertyGroup root = config.loadConfigurationForTenant(tenant); + PropertyGroup rsrcsGroup = root == null ? null : root.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES); + result.put(tenant, computeTenantSPs(rsrcsGroup)); + } + // restore the default tenantId just in case + FHIRRequestContext.get().setTenantId(FHIRConfiguration.DEFAULT_TENANT_ID); - ParametersMap map = typeToParamMap.get(base); - if (map == null) { - map = new ParametersMap(); - typeToParamMap.put(base, map); + return result; + } + + private static Map computeTenantSPs(PropertyGroup rsrcsGroup) throws Exception { + Map parametersByType = new HashMap<>(); + Set resourceTypesWithWildcardParams = new HashSet<>(); + boolean supportOmittedRsrcTypes = true; + + if (rsrcsGroup != null) { + List rsrcsEntries = rsrcsGroup.getProperties(); + if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) { + for (PropertyEntry rsrcsEntry : rsrcsEntries) { + ParametersMap paramMap = new ParametersMap(); + + // Check special property for including omitted resource types + if (FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(rsrcsEntry.getName())) { + if (rsrcsEntry.getValue() instanceof Boolean) { + supportOmittedRsrcTypes = (Boolean) rsrcsEntry.getValue(); + } else { + throw SearchExceptionUtil.buildNewIllegalStateException(); + } + } else { + String resourceType = rsrcsEntry.getName(); + PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue(); + if (resourceTypeGroup != null) { + // Get search parameters + PropertyGroup spGroup = resourceTypeGroup.getPropertyGroup(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_PARAMETERS); + if (spGroup != null) { + List spEntries = spGroup.getProperties(); + if (spEntries != null && !spEntries.isEmpty()) { + for (PropertyEntry spEntry : spEntries) { + String code = spEntry.getName(); + if (SearchConstants.WILDCARD.equals(code)) { + resourceTypesWithWildcardParams.add(resourceType); + } else if (spEntry.getValue() instanceof String) { + SearchParameter sp = FHIRRegistry.getInstance() + .getResource((String)spEntry.getValue(), SearchParameter.class); + if (sp != null) { + if (sp.getExpression() == null || !sp.getExpression().hasValue()) { + if (log.isLoggable(Level.FINE)) { + log.fine(String.format(MISSING_EXPRESSION_WARNING, sp.getCode().getValue())); + } + } else { + paramMap.insert(code, sp); + } + } + } + } + } + } else { + resourceTypesWithWildcardParams.add(resourceType); + } + } + parametersByType.put(resourceType, paramMap); } + } + } + } - // check and warn if the parameter name and code do not agree. - String code = parameter.getCode().getValue(); - String name = parameter.getName().getValue(); - checkAndWarnForIssueWithCodeAndName(code, name); + if (supportOmittedRsrcTypes) { + // All other resource types include all search parameters + for (String resourceType : ALL_RESOURCE_TYPES) { + if (!parametersByType.containsKey(resourceType)) { + resourceTypesWithWildcardParams.add(resourceType); + } + } + } - // add the map entry with keys for both the code and the url - map.insert(code, parameter); + for (SearchParameter sp : getAllSearchParameters()) { + for (ResourceType resourceType : sp.getBase()) { + if (resourceTypesWithWildcardParams.contains(resourceType.getValue()) && sp.getCode().hasValue()) { + if (sp.getExpression() == null || !sp.getExpression().hasValue()) { + if (log.isLoggable(Level.FINE)) { + log.fine(String.format(MISSING_EXPRESSION_WARNING, sp.getCode().getValue())); + } + } else { + ParametersMap paramMap = parametersByType.get(resourceType.getValue()); + if (paramMap == null) { + paramMap = new ParametersMap(); + parametersByType.put(resourceType.getValue(), paramMap); + } + paramMap.insert(sp.getCode().getValue(), sp); + } } } } - // Return an unmodifiable copy, lest there be side effects. - return Collections.unmodifiableMap(typeToParamMap); + return parametersByType; + } + + public static Map getTenantSPs(String tenant) { + return searchParameters.get(tenant); } - private static List getSearchParameters() { - List searchParameters = new ArrayList<>(2048); + public static Set getAllSearchParameters() { + Set searchParameters = new LinkedHashSet<>(2048); for (SearchParamType.Value searchParamType : SearchParamType.Value.values()) { + searchParameters.addAll(FHIRRegistry.getInstance().getSearchParameters(searchParamType.value())); } return searchParameters; @@ -211,25 +294,20 @@ static void checkAndWarnForIssueWithCodeAndName(String code, String name) { } } - /** - * @return a map of maps - * The outer map is keyed by resource type. - * The inner map is keyed by both SearchParameter.code and SearchParameter.url - */ - public static Map getBuiltInSearchParametersMap() { - return builtInSearchParameters; - } - /** * convenience method to print the output of the Search Parameters. * * @param out */ public static void print(PrintStream out) { - print(out, builtInSearchParameters); + for (String tenant : searchParameters.keySet()) { + out.println(SearchConstants.LOG_BOUNDARY); + out.println("- TENANT " + tenant); + print(out, searchParameters.get(tenant)); + } } - /* + /** * used when locally debugging. * @param out * @param searchParamsMap diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/cache/TenantSpecificSearchParameterCache.java b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/cache/TenantSpecificSearchParameterCache.java index 374c842a26c..e4cad3622fd 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/cache/TenantSpecificSearchParameterCache.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/cache/TenantSpecificSearchParameterCache.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2020 + * (C) Copyright IBM Corp. 2019, 2021 * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,29 +8,30 @@ import java.io.File; import java.io.FileReader; +import java.io.IOException; import java.io.Reader; -import java.util.Map; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.core.TenantSpecificFileBasedCache; -import com.ibm.fhir.exception.FHIROperationException; +import com.ibm.fhir.exception.FHIRException; import com.ibm.fhir.model.format.Format; import com.ibm.fhir.model.parser.FHIRParser; import com.ibm.fhir.model.resource.Bundle; -import com.ibm.fhir.search.parameters.ParametersMap; -import com.ibm.fhir.search.parameters.ParametersUtil; +import com.ibm.fhir.model.resource.SearchParameter; /** * This class implements a cache of SearchParameters organized by tenantId. Each object stored in the cache will be a - * two-level map of SearchParameters organized first by resource type, then by search parameter code. + * list of SearchParameters. * * Note: While we support json format only, to enable XML, it's best to create a new cache specific to XML. This change * should change one line in this class, and be instantiated in the SearchUtil, and embedded in the call to Parameters. * Alternatively, one could, upon not finding the JSON file, load the XML file. */ -public class TenantSpecificSearchParameterCache extends TenantSpecificFileBasedCache> { +public class TenantSpecificSearchParameterCache extends TenantSpecificFileBasedCache> { private static final String CLASSNAME = TenantSpecificSearchParameterCache.class.getName(); private static final Logger log = Logger.getLogger(CLASSNAME); @@ -39,7 +40,7 @@ public class TenantSpecificSearchParameterCache extends TenantSpecificFileBasedC private static final String CACHE_NAME = "SearchParameters"; - private static final String OPERATION_EXCEPTION = "An error occurred while loading one of the tenant files: %s"; + private static final String EXCEPTION_MESSAGE = "An error occurred while loading one of the tenant files: %s"; private static final String LOG_FILE_LOAD = "The file loaded is [%s]"; @@ -53,17 +54,21 @@ public String getCacheEntryFilename(String tenantId) { } @Override - public Map createCachedObject(File f) throws Exception { + public List createCachedObject(File f) throws Exception { // Added logging to help diagnose issues while loading the files. if (log.isLoggable(Level.FINE)) { log.fine(String.format(LOG_FILE_LOAD, f.toURI())); } - try (Reader reader = new FileReader(f);) { - // Default is to use JSON in R4 + + try (Reader reader = new FileReader(f)) { + // Default is to use JSON Bundle bundle = FHIRParser.parser(Format.JSON).parse(reader); - return ParametersUtil.buildSearchParametersMapFromBundle(bundle); - } catch (Throwable t) { - throw new FHIROperationException(String.format(OPERATION_EXCEPTION, f.getAbsolutePath()), t); + return bundle.getEntry().stream() + .filter(e -> e.getResource() != null && e.getResource().is(SearchParameter.class)) + .map(e -> e.getResource().as(SearchParameter.class)) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new FHIRException(String.format(EXCEPTION_MESSAGE, f.getAbsolutePath()), e); } } } \ No newline at end of file diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java index b234f37158a..efd93060efc 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java @@ -8,7 +8,6 @@ import static com.ibm.fhir.model.util.ModelSupport.FHIR_STRING; -import java.io.FileNotFoundException; import java.math.BigDecimal; import java.net.URISyntaxException; import java.net.URLEncoder; @@ -20,7 +19,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -79,7 +77,6 @@ import com.ibm.fhir.search.parameters.ParametersUtil; import com.ibm.fhir.search.parameters.QueryParameter; import com.ibm.fhir.search.parameters.QueryParameterValue; -import com.ibm.fhir.search.parameters.cache.TenantSpecificSearchParameterCache; import com.ibm.fhir.search.reference.value.CompartmentReference; import com.ibm.fhir.search.sort.Sort; import com.ibm.fhir.search.uri.UriBuilder; @@ -98,8 +95,6 @@ public class SearchUtil { // Logging Strings private static final String EXTRACT_PARAMETERS_LOGGING = "extractParameterValues: [%s] [%s]"; - private static final String NO_TENANT_SP_MAP_LOGGING = - "No tenant-specific search parameters found for tenant '%s'; trying %s "; private static final String UNSUPPORTED_EXCEPTION = "Search Parameter includes an unsupported operation or bad expression : [%s] [%s] [%s]"; @@ -143,21 +138,7 @@ public class SearchUtil { // The functionality is split into a new class. private static final Sort sort = new Sort(); - /* - * This is our in-memory cache of SearchParameter objects. The cache is organized at the top level by tenant-id, - * with the built-in (FHIR spec-defined) SearchParameters stored under the "built-in" pseudo-tenant-id. - * SearchParameters contained in the default tenant's extension-search-parameters.json file are stored under the - * "default" tenant-id, and other tenants' SearchParameters (defined in their tenant-specific - * extension-search-parameters.json files) will be stored under their respective tenant-ids as well. The objects - * stored in our cache are of type CachedObjectHolder, with each one containing a Map>. This map is keyed by resource type (simple name, e.g. "Patient"). Each object stored in this - * map contains the SearchParameters for that resource type, keyed by SearchParameter name (e.g. "_lastUpdated"). - * When getSearchParameter(resourceType, name) is called, we'll need to first search in the current tenant's map, - * then if not found, look in the "built-in" tenant's map. Also, when getSearchParameters(resourceType) is called, - * we'll need to return a List that contains SearchParameters from the current tenant's map (if present) plus those - * contained in the "built-in" tenant's map as well. - */ - private static TenantSpecificSearchParameterCache searchParameterCache = new TenantSpecificSearchParameterCache(); + private SearchUtil() { // No Operation @@ -177,265 +158,9 @@ public static void init() { // Loads the Compartments CompartmentUtil.init(); - // Loads the Parameters into a map ParametersUtil.init(); } - /** - * Retrieves user-defined SearchParameters associated with the specified - * resource type and current tenant id. - * - * @param resourceType - * the resource type for which user-defined SearchParameters will be returned - * @return a list of user-defined SearchParameters associated with the specified resource type - * @throws Exception - */ - protected static List getUserDefinedSearchParameters(String resourceType) throws Exception { - List result = new ArrayList<>(); - String tenantId = FHIRRequestContext.get().getTenantId(); - Map spMapTenant = getTenantOrDefaultSPMap(tenantId); - - if (spMapTenant != null) { - ParametersMap spMapResourceType = spMapTenant.get(resourceType); - if (spMapResourceType != null && !spMapResourceType.isEmpty()) { - result.addAll(spMapResourceType.values()); - } - - // Add the Mapping to All Resource Types - spMapResourceType = spMapTenant.get(SearchConstants.RESOURCE_RESOURCE); - if (spMapResourceType != null && !spMapResourceType.isEmpty()) { - result.addAll(spMapResourceType.values()); - } - } - return result; - } - - /** - * Returns a filtered list of built-in SearchParameters associated with the - * specified resource type and those associated with the "Resource" resource type. - * - * @param resourceType - * the resource type - * @return a filtered list of SearchParameters - * @throws Exception - */ - protected static List getFilteredBuiltinSearchParameters(String resourceType) throws Exception { - List result = new ArrayList<>(); - - Map spBuiltin = ParametersUtil.getBuiltInSearchParametersMap(); - - // Retrieve the current tenant's search parameter filtering rules. - Map> filterRules = getFilterRules(); - - // Retrieve the SPs associated with the specified resource type and filter per the filter rules. - ParametersMap spMap = spBuiltin.get(resourceType); - if (spMap != null && !spMap.isEmpty()) { - result.addAll(filterSearchParameters(filterRules, resourceType, spMap)); - } - - if (!SearchConstants.RESOURCE_RESOURCE.equals(resourceType)) { - // Retrieve the SPs associated with the "Resource" resource type and filter per the filter rules. - spMap = spBuiltin.get(SearchConstants.RESOURCE_RESOURCE); - if (spMap != null && !spMap.isEmpty()) { - Collection superParams = - filterSearchParameters(filterRules, SearchConstants.RESOURCE_RESOURCE, spMap); - Map resultCodes = result.stream() - .collect(Collectors.toMap(sp -> sp.getCode().getValue(), sp -> sp)); - - for (SearchParameter sp : superParams) { - String spCode = sp.getCode().getValue(); - if (resultCodes.containsKey(spCode)) { - SearchParameter existingSP = resultCodes.get(spCode); - if (sp.getExpression() != null && !sp.getExpression().equals(existingSP.getExpression())) { - log.warning("Code '" + sp.getCode().getValue() + "' is defined for " + - SearchConstants.RESOURCE_RESOURCE + " and " + resourceType + - " with differing expressions; using " + resourceType); - } - } else { - result.add(sp); - } - } - } - } - - return result; - } - - /** - * Filters the specified input list of SearchParameters according to the filter rules and input resource type. - * The filter rules are contained in a Map> that is keyed by resource type. - * The value of each Map entry is a list of search parameter names that should be included in our filtered result. - * - * @param allFilterRules - * a Map containing filter rules for all resource types - * @param resourceType - * the resource type associated with each of the unfiltered SearchParameters - * @param unfilteredSearchParameters - * the ParametersMap of unfiltered SearchParameter objects for this resource type - * @return a filtered Collection of SearchParameters - * @implSpec This method guarantees that each search parameter returned in the Collection will have a unique code. - */ - private static Collection filterSearchParameters(Map> allFilterRules, - String resourceType, ParametersMap unfilteredSearchParameters) { - Map results = new HashMap<>(); - - // First, retrieve the filter rule (list of SP urls to be included) for the specified resource type. - // We know that the SearchParameters in the unfiltered list are all associated with this resource type, - // so we can use this same "url list" for each Search Parameter in the unfiltered list. - Map filterRules = allFilterRules.get(resourceType); - - if (filterRules == null) { - // If the specified resource type wasn't found in the Map then retrieve the wildcard entry if present. - filterRules = allFilterRules.get(SearchConstants.WILDCARD_FILTER); - } - - // If we found a non-empty list of search parameters to filter on, then do the filtering. - if (filterRules != null && !filterRules.isEmpty()) { - boolean includeAll = false; - - for (Entry filterRule : filterRules.entrySet()) { - if (SearchConstants.WILDCARD_FILTER.equals(filterRule.getValue())) { - includeAll = true; - } else { - SearchParameter sp = unfilteredSearchParameters.lookupByCanonical(filterRule.getValue()); - if (sp != null) { - results.put(filterRule.getKey(), sp); - } - } - } - - if (includeAll) { - // Add all search parameters that don't conflict with the configured list - for (Entry> spByCode : unfilteredSearchParameters.codeEntries()) { - String code = spByCode.getKey(); - Set spSet = spByCode.getValue(); - - if (results.containsKey(code)) { - log.fine("Skipping search parameters for code '" + code + "' on resource type '" + resourceType - + "' because the configured set contains " + results.get(code)); - } else { - if (spSet.size() > 1) { - StringBuilder canonicalUrlSetStringBuilder = new StringBuilder("["); - String delim = ""; - for (SearchParameter sp : spSet) { - canonicalUrlSetStringBuilder.append(delim); - canonicalUrlSetStringBuilder.append(sp.getUrl().getValue()); - if (sp.getVersion() != null) { - canonicalUrlSetStringBuilder.append("|").append(sp.getVersion().getValue()); - } - delim = ", "; - } - String canonicalUrls = canonicalUrlSetStringBuilder.append("]").toString(); - - log.warning("Found multiple search parameters for code '" + code - + "' on resource type '" + resourceType + "': " + canonicalUrls - + "; use search parameter filtering to disambiguate."); - } - results.put(code, spSet.iterator().next()); - } - } - } - } - - return results.values(); - } - - /** - * Retrieves the search parameter filtering rules for the current tenant. - * - * @return a map of resource types to allowed search parameters; - * the first map is keyed by resource type ('*' for all resource types) - * and the second map is keyed by search parameter code ('*':'*' for all applicable built-in parameters). - * @throws Exception an exception - */ - private static Map> getFilterRules() throws Exception { - Map> result = new HashMap<>(); - boolean supportOmittedRsrcTypes = true; - - // Retrieve the "resources" config property group. - PropertyGroup rsrcsGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES); - if (rsrcsGroup != null) { - List rsrcsEntries = rsrcsGroup.getProperties(); - if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) { - for (PropertyEntry rsrcsEntry : rsrcsEntries) { - - // Check special property for including omitted resource types - if (FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(rsrcsEntry.getName())) { - if (rsrcsEntry.getValue() instanceof Boolean) { - supportOmittedRsrcTypes = (Boolean) rsrcsEntry.getValue(); - } else { - throw SearchExceptionUtil.buildNewIllegalStateException(); - } - } - else { - String resourceType = rsrcsEntry.getName(); - PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue(); - if (resourceTypeGroup != null) { - Map searchParameterUrls = new HashMap<>(); - - // Get search parameters - PropertyGroup spGroup = resourceTypeGroup.getPropertyGroup(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_PARAMETERS); - if (spGroup != null) { - List spEntries = spGroup.getProperties(); - if (spEntries != null && !spEntries.isEmpty()) { - for (PropertyEntry spEntry : spEntries) { - searchParameterUrls.put(spEntry.getName(), (String) spEntry.getValue()); - } - } - } else { - searchParameterUrls.put(SearchConstants.WILDCARD, SearchConstants.WILDCARD); - } - result.put(resourceType, searchParameterUrls); - } - } - } - } - } - - if (supportOmittedRsrcTypes) { - // All other resource types include all search parameters - result.put(SearchConstants.WILDCARD, Collections.singletonMap(SearchConstants.WILDCARD, SearchConstants.WILDCARD)); - } - - return result; - } - - /** - * Returns the extended search parameters for the specified tenant-id, the default tenant, - * or null if there are no extended search parameters. - * - * @param tenantId - * the tenant-id whose SearchParameters should be returned. - * @return a map of ParametersMap objects keyed by resource type - * @throws FileNotFoundException - */ - private static Map getTenantOrDefaultSPMap(String tenantId) throws Exception { - if (log.isLoggable(Level.FINEST)) { - log.entering(CLASSNAME, "getTenantSPMap", new Object[] { tenantId }); - } - try { - Map cachedObjectForTenant = - searchParameterCache.getCachedObjectForTenant(tenantId); - - if (cachedObjectForTenant == null) { - - // Output logging detail. - if (log.isLoggable(Level.FINER)) { - log.finer(String.format(NO_TENANT_SP_MAP_LOGGING, tenantId, FHIRConfiguration.DEFAULT_TENANT_ID)); - } - - cachedObjectForTenant = - searchParameterCache.getCachedObjectForTenant(FHIRConfiguration.DEFAULT_TENANT_ID); - } - - return cachedObjectForTenant; - } finally { - if (log.isLoggable(Level.FINEST)) { - log.exiting(CLASSNAME, "getTenantSPMap"); - } - } - } - /** * @param resourceType * @param code @@ -453,117 +178,33 @@ public static SearchParameter getSearchParameter(Class resourceType, String c * @throws Exception */ public static SearchParameter getSearchParameter(String resourceType, String code) throws Exception { - SearchParameter result = null; - - Map> filterRules = getFilterRules(); - Map targetResourceFilterRules = filterRules.get(resourceType); - Map parentResourceFilterRules = filterRules.get(SearchConstants.WILDCARD); + Set result = new HashSet<>(); String tenantId = FHIRRequestContext.get().getTenantId(); - Map tenantSpMap = getTenantOrDefaultSPMap(tenantId); - - if (targetResourceFilterRules != null && targetResourceFilterRules.containsKey(code)) { - Canonical uri = Canonical.of(targetResourceFilterRules.get(code)); - result = getSearchParameterByUrlFromTenantOrBuiltIn(resourceType, code, tenantSpMap, uri); - } else if (parentResourceFilterRules != null && parentResourceFilterRules.containsKey(code)) { - Canonical uri = Canonical.of(parentResourceFilterRules.get(code)); - result = getSearchParameterByUrlFromTenantOrBuiltIn(resourceType, code, tenantSpMap, uri); - } else if (targetResourceFilterRules == null || targetResourceFilterRules.containsKey(SearchConstants.WILDCARD)) { - Set params = getSearchParametersByCodeFromTenantOrBuiltIn(resourceType, code, tenantSpMap); - - if (params != null && !params.isEmpty()) { - Iterator iterator = params.iterator(); - result = iterator.next(); - String configuredUrl = result.getUrl().getValue(); - while (iterator.hasNext()) { - SearchParameter conflict = iterator.next(); - log.warning("Found multiple resource-specific search parameters, '" + configuredUrl + "' and '" + conflict.getUrl().getValue() - + "', for code '" + code + "' on resource type '" + resourceType + "';" - + " use search parameter filtering to disambiguate. Using '" + configuredUrl + "'."); - } - } - } else if (parentResourceFilterRules == null || parentResourceFilterRules.containsKey(SearchConstants.WILDCARD)) { - Set params = getSearchParametersByCodeFromTenantOrBuiltIn(SearchConstants.RESOURCE_RESOURCE, code, tenantSpMap); - - if (params != null && !params.isEmpty()) { - Iterator iterator = params.iterator(); - result = iterator.next(); - String configuredUrl = result.getUrl().getValue(); - while (iterator.hasNext()) { - SearchParameter conflict = iterator.next(); - log.warning("Found multiple cross-resource search parameters, '" + configuredUrl + "' and '" + conflict.getUrl().getValue() - + "', for code '" + code + "'; use search parameter filtering to disambiguate. Using '" + configuredUrl + "'."); - } - } - } - if (result == null && log.isLoggable(Level.FINE)) { - log.fine("SearchParameter with code '" + code + "' on resource type " + resourceType + " was not found."); + Map paramsByResourceType = ParametersUtil.getTenantSPs(tenantId); + if (paramsByResourceType == null) { + return null; } - return result; - } - - /** - * This private method does not apply filtering because it is expected to be used directly from processing the filter. - */ - private static SearchParameter getSearchParameterByUrlFromTenantOrBuiltIn(String resourceType, String code, Map tenantSpMap, - Canonical uri) { - // First try to find the search parameter within the specified tenant's map. - SearchParameter result = getSearchParameterByUrlIfPresent(tenantSpMap, resourceType, uri); - - // If we didn't find it within the tenant's map, then look within the built-in map. - if (result == null) { - result = getSearchParameterByUrlIfPresent(ParametersUtil.getBuiltInSearchParametersMap(), resourceType, uri); + for (String type : new String[]{resourceType, SearchConstants.RESOURCE_RESOURCE}) { + ParametersMap parametersMap = paramsByResourceType.get(type); + if (parametersMap != null) { + Set searchParams = parametersMap.lookupByCode(code); + if (searchParams != null) { + result.addAll(searchParams); + } + } } - if (result == null) { - log.warning("Configured search parameter with url '" + uri.getValue() + "' for code '" + code + - "' on resource type '" + resourceType + "' could not be found." ); + if (result.isEmpty()) { + return null; } - return result; - } - - /** - * This private method does not apply filtering because it is expected to be used directly from processing the filter. - */ - private static Set getSearchParametersByCodeFromTenantOrBuiltIn(String resourceType, String code, Map tenantSpMap) { - // First try to find the search parameters within the specified tenant's map. - Set params = getSearchParametersByCodeIfPresent(tenantSpMap, resourceType, code); - - // If we didn't find any within the tenant's map, then look within the built-in map. - if (params == null || params.isEmpty()) { - params = getSearchParametersByCodeIfPresent(ParametersUtil.getBuiltInSearchParametersMap(), resourceType, code); + if (result.size() > 1) { + log.info("Found multiple search parameters for code " + code); } - return params; - } - /** - * This private method does not apply filtering because it is expected to be used directly from processing the filter. - * - * @param spMaps - * @param resourceType - * @param code - * @return the SearchParameter for type {@code resourceType} with code {@code code} or null if it doesn't exist - */ - private static Set getSearchParametersByCodeIfPresent(Map spMaps, String resourceType, String code) { - Set result = null; - - if (spMaps != null && !spMaps.isEmpty()) { - ParametersMap parametersMap = spMaps.get(resourceType); - if (parametersMap != null && !parametersMap.isEmpty()) { - result = parametersMap.lookupByCode(code); - } - - if (result == null) { - parametersMap = spMaps.get(SearchConstants.RESOURCE_RESOURCE); - if (parametersMap != null && !parametersMap.isEmpty()) { - result = parametersMap.lookupByCode(code); - } - } - } - - return result; + return result.iterator().next(); } /** @@ -586,55 +227,8 @@ public static SearchParameter getSearchParameter(String resourceType, Canonical String tenantId = FHIRRequestContext.get().getTenantId(); // First try to find the search parameter within the specified tenant's map. - SearchParameter result = getSearchParameterByUrlIfPresent(getTenantOrDefaultSPMap(tenantId), resourceType, uri); - - // If we didn't find it within the tenant's map, then look within the built-in map. - if (result == null) { - result = getSearchParameterByUrlIfPresent(ParametersUtil.getBuiltInSearchParametersMap(), resourceType, uri); - - // If we found it within the built-in search parameters, apply our filtering rules. - if (result != null) { - - // Check if this search parameter applies to the base Resource type - ResourceType rt = result.getBase().get(0); - if (SearchConstants.RESOURCE_RESOURCE.equals(rt.getValue())) { - resourceType = rt.getValue(); - } + SearchParameter result = getSearchParameterByUrlIfPresent(ParametersUtil.getTenantSPs(tenantId), resourceType, uri); - Map> allFilterRules = getFilterRules(); - Map filterRules = getFilterRules().get(resourceType); - if (filterRules == null) { - // If the specified resource type wasn't found in the Map then retrieve the wildcard entry if present. - filterRules = allFilterRules.get(SearchConstants.WILDCARD_FILTER); - } - - // If we found a non-empty list of search parameters to filter on, then do the filtering. - if (filterRules != null && !filterRules.isEmpty()) { - boolean includeAll = false; - - for (Entry filterRule : filterRules.entrySet()) { - if (SearchConstants.WILDCARD_FILTER.equals(filterRule.getValue())) { - includeAll = true; - } else if (result.getCode().getValue().equals(filterRule.getKey())) { - String url = result.getUrl().getValue(); - - if (filterRule.getValue().equals(uri.getValue()) || filterRule.getValue().equals(url)) { - return result; - } else { - // Configuration has mapped this code to a different search parameter - return null; - } - } - } - - // If we got here, then we havn't found an explicit rule for this search parameter - // so filter out the result if there's no applicable wildcard rule - if (!includeAll) { - result = null; - } - } - } - } return result; } @@ -692,7 +286,7 @@ private static Map getInclusionWildcardSearchParameters String searchParameterTargetType, String inclusionKeyword, Modifier modifier) throws Exception { Map inclusionSearchParameters = new HashMap<>(); - for (SearchParameter searchParameter : getApplicableSearchParameters(joinResourceType)) { + for (SearchParameter searchParameter : getSearchParameters(joinResourceType)) { if (SearchParamType.REFERENCE.equals(searchParameter.getType()) && ((SearchConstants.INCLUDE.equals(inclusionKeyword) && (searchParameterTargetType == null || isValidTargetType(searchParameterTargetType, searchParameter))) || @@ -745,7 +339,7 @@ public static Map> extractParameterValues(Re FHIRPathEvaluator evaluator = FHIRPathEvaluator.evaluator(); EvaluationContext evaluationContext = new EvaluationContext(resource); - List parameters = getApplicableSearchParameters(resourceType.getSimpleName()); + List parameters = getSearchParameters(resourceType.getSimpleName()); for (SearchParameter parameter : parameters) { @@ -1564,12 +1158,24 @@ protected static boolean isAllowed(Type type, Modifier modifier) { /** * Returns a list of SearchParameters that consist of those associated with the - * "Resource" base resource type, as - * well as those associated with the specified resource type. + * "Resource" base resource type, as well as those associated with the specified resource type. */ - public static List getApplicableSearchParameters(String resourceType) throws Exception { - List result = getFilteredBuiltinSearchParameters(resourceType); - result.addAll(getUserDefinedSearchParameters(resourceType)); + public static List getSearchParameters(String resourceType) throws Exception { + List result = new ArrayList<>(); + + String tenantId = FHIRRequestContext.get().getTenantId(); + Map paramsByResourceType = ParametersUtil.getTenantSPs(tenantId); + if (paramsByResourceType == null) { + return null; + } + + for (String type : new String[]{resourceType, SearchConstants.RESOURCE_RESOURCE}) { + ParametersMap parametersMap = paramsByResourceType.get(type); + if (parametersMap != null) { + result.addAll(parametersMap.values()); + } + } + return result; } diff --git a/fhir-search/src/main/resources/META-INF/services/com.ibm.fhir.registry.spi.FHIRRegistryResourceProvider b/fhir-search/src/main/resources/META-INF/services/com.ibm.fhir.registry.spi.FHIRRegistryResourceProvider new file mode 100644 index 00000000000..4db5dee5bce --- /dev/null +++ b/fhir-search/src/main/resources/META-INF/services/com.ibm.fhir.registry.spi.FHIRRegistryResourceProvider @@ -0,0 +1 @@ +com.ibm.fhir.search.parameters.ExtensionSearchParametersResourceProvider \ No newline at end of file diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentUtilTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentUtilTest.java index a91371ad1ec..0bc8c696bfe 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentUtilTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentUtilTest.java @@ -44,7 +44,7 @@ public void testBuildCompartmentMap() { } - @Test(expectedExceptions = {}) + @Test public void testGetCompartmentResourceTypeExists() throws FHIRSearchException { // The Compartment Does not Exist Exists List results = CompartmentUtil.getCompartmentResourceTypes("Patient"); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/MultiTenantSearchParameterTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/MultiTenantSearchParameterTest.java index 5464024e7c8..1171ed7f48b 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/MultiTenantSearchParameterTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/MultiTenantSearchParameterTest.java @@ -16,16 +16,13 @@ import java.util.stream.Collectors; import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.FHIRRequestContext; import com.ibm.fhir.exception.FHIRException; import com.ibm.fhir.model.resource.Basic; import com.ibm.fhir.model.resource.Device; import com.ibm.fhir.model.resource.Observation; -import com.ibm.fhir.model.resource.Patient; import com.ibm.fhir.model.resource.SearchParameter; import com.ibm.fhir.search.test.BaseSearchTest; import com.ibm.fhir.search.util.SearchUtil; @@ -35,12 +32,6 @@ */ public class MultiTenantSearchParameterTest extends BaseSearchTest { - @Override - @BeforeClass - public void setup() { - FHIRConfiguration.setConfigHome("target/test-classes"); - } - @AfterMethod public void cleanup() throws FHIRException { // Restore the threadLocal FHIRRequestContext to the default tenant @@ -55,7 +46,7 @@ public void testGetApplicableSearchParameters2() throws Exception { FHIRRequestContext.get().setTenantId("tenant1"); // Using Medication because tenant1 has filters in place for Patient and Observation - List result = SearchUtil.getApplicableSearchParameters("Medication"); + List result = SearchUtil.getSearchParameters("Medication"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters2", result); assertEquals(15, result.size()); @@ -65,7 +56,7 @@ public void testGetApplicableSearchParameters2() throws Exception { public void testGetApplicableSearchParameters1() throws Exception { // Simple test looking only for built-in search parameters for Observation.class. // Use default tenant id ("default") which has no Observation tenant-specifc search parameters. - List result = SearchUtil.getApplicableSearchParameters("Observation"); + List result = SearchUtil.getSearchParameters("Observation"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters1", result); assertEquals(44, result.size()); @@ -78,12 +69,12 @@ public void testGetApplicableSearchParameters3() throws Exception { // Use the default tenant since it has some Patient search parameters defined. FHIRRequestContext.get().setTenantId("default"); - List result = SearchUtil.getApplicableSearchParameters("Patient"); + List result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters3/Patient", result); assertEquals(37, result.size()); - result = SearchUtil.getApplicableSearchParameters("Observation"); + result = SearchUtil.getSearchParameters("Observation"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters3/Observation", result); assertEquals(44, result.size()); @@ -93,11 +84,11 @@ public void testGetApplicableSearchParameters3() throws Exception { public void testGetApplicableSearchParameters4() throws Exception { // Looking for built-in and tenant-specific search parameters for "Observation". - // Use tenant1 since it has some Patient search parameters defined. + // Use tenant1 since it has some Observation search parameters defined. FHIRRequestContext.get().setTenantId("tenant1"); - // tenant1's filtering includes only 1 search parameter for Observation. - List result = SearchUtil.getApplicableSearchParameters("Observation"); + // tenant1's filtering includes 2 search parameters for Observation. + List result = SearchUtil.getSearchParameters("Observation"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters4/Observation", result); assertEquals(8, result.size()); @@ -111,7 +102,7 @@ public void testGetApplicableSearchParameters5() throws Exception { // Test filtering of search parameters for Device (tenant1). FHIRRequestContext.get().setTenantId("tenant1"); - List result = SearchUtil.getApplicableSearchParameters("Device"); + List result = SearchUtil.getSearchParameters("Device"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters5/Device", result); assertEquals(8, result.size()); @@ -125,7 +116,7 @@ public void testGetApplicableSearchParameters6() throws Exception { // Test filtering of search parameters for Patient (tenant1). FHIRRequestContext.get().setTenantId("tenant1"); - List result = SearchUtil.getApplicableSearchParameters("Patient"); + List result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters6/Patient", result); assertEquals(10, result.size()); @@ -137,7 +128,7 @@ public void testGetApplicableSearchParameters6() throws Exception { // Make sure we get all of the MedicationAdministration search parameters. // (No filtering configured for these) - result = SearchUtil.getApplicableSearchParameters("MedicationAdministration"); + result = SearchUtil.getSearchParameters("MedicationAdministration"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters6/MedicationAdministration", result); assertEquals(19, result.size()); @@ -148,33 +139,34 @@ public void testGetApplicableSearchParameters7() throws Exception { // Test filtering of search parameters for Patient (default tenant). FHIRRequestContext.get().setTenantId("default"); - List result = SearchUtil.getApplicableSearchParameters("Patient"); + List result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters7/Patient", result); assertEquals(37, result.size()); - result = SearchUtil.getApplicableSearchParameters("Device"); + result = SearchUtil.getSearchParameters("Device"); assertNotNull(result); printSearchParameters("testGetApplicableSearchParameters7/Device", result); assertEquals(20, result.size()); } @Test - public void testGetSearchParameters2() throws Exception { + public void testGetSearchParameters() throws Exception { // Looking only for built-in search parameters for "Patient". // Use tenant3 since it doesn't have any tenant-specific search parameters. FHIRRequestContext.get().setTenantId("tenant3"); - List result = SearchUtil.getApplicableSearchParameters("Patient"); + List result = SearchUtil.getSearchParameters("Patient"); + printSearchParameters("testGetSearchParameters", result); + System.out.println("tenant: " + FHIRRequestContext.get().getTenantId()); assertNotNull(result); - printSearchParameters("testGetSearchParameters2", result); assertEquals(31, result.size()); } @Test public void testGetSearchParameter1() throws Exception { - // Use tenant1 since it has some Patient search parameters defined. + // Use tenant1 since it has some Basic search parameters defined. FHIRRequestContext.get().setTenantId("tenant1"); SearchParameter result = SearchUtil.getSearchParameter(Basic.class, "measurement-type"); @@ -287,124 +279,12 @@ public void testGetSearchParameter12() throws Exception { assertNotNull(result); } - @Test - void testDynamicSearchParameters1() throws Exception { - // Test behavior of dynamic updates to search parameters. - FHIRRequestContext.get().setTenantId("tenant2"); - - String mainFile = "target/test-classes/config/tenant2/extension-search-parameters.json"; - String hiddenFile1 = mainFile + ".hide1"; - String hiddenFile2 = mainFile + ".hide2"; - - // First, remove any copy of the main file that might exist. - // Our initial state will be no tenant-specific search parameters present. - deleteFile(mainFile); - - // Next, let's make sure we cannot find either of the tenant-specific search parameters. - SearchParameter searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-mlb-team"); - assertNull(searchParameter); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-nfl-team"); - assertNull(searchParameter); - - // Sleep a bit to allow file mod times to register. - Thread.sleep(1000); - - // Copy our first hidden file into place. - // This should add two tenant-specific search parameters. - copyFile(hiddenFile1, mainFile); - - // Verify that we can now "see" the two tenant-specific search parameters. - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-mlb-team"); - assertNotNull(searchParameter); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-nfl-team"); - assertNotNull(searchParameter); - - // Sleep a bit to allow file mod times to register. - Thread.sleep(1000); - - // Next, copy our second hidden file into place. - // This should effectively remove the "favorite-nfl-team" search parameter. - copyFile(hiddenFile2, mainFile); - - // Verify that we can now "see" only the first tenant-specific search parameter. - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-mlb-team"); - assertNotNull(searchParameter); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-nfl-team"); - assertNull(searchParameter); - - // Finally, remove the tenant-specific file altogether and make sure we - // can't find either search parameter again. - deleteFile(mainFile); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-mlb-team"); - assertNull(searchParameter); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-nfl-team"); - assertNull(searchParameter); - } - - @Test - void testDynamicSearchParameters2() throws Exception { - // Test behavior related to falling back to default tenant. - FHIRRequestContext.get().setTenantId("tenant2"); - - String mainFile = "target/test-classes/config/tenant2/extension-search-parameters.json"; - String hiddenFile1 = mainFile + ".hide1"; - - // First, remove any copy of the main file that might exist. - // Our initial state will be no tenant-specific search parameters present. - deleteFile(mainFile); - - // With no tenant-specific search parameters, we should fall back to the default tenant which defines patient - // extensions - SearchParameter searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-mlb-team"); - assertNull(searchParameter); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-color"); - assertNotNull(searchParameter); - - List result = SearchUtil.getApplicableSearchParameters("Patient"); - assertNotNull(result); - printSearchParameters("testDynamicSearchParameters2/Patient", result); - assertEquals(37, result.size()); - - // Sleep a bit to allow file mod times to register. - Thread.sleep(100); - - // Copy our first hidden file into place. - // This should add two tenant-specific search parameters. - copyFile(hiddenFile1, mainFile); - - // Verify that we can now see the two tenant-specific search parameters and NOT the default tenant ones - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-mlb-team"); - assertNotNull(searchParameter); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-color"); - assertNull(searchParameter); - - result = SearchUtil.getApplicableSearchParameters("Patient"); - assertNotNull(result); - printSearchParameters("testDynamicSearchParameters2/Patient", result); - assertEquals(33, result.size()); - - // Sleep a bit to allow file mod times to register. - Thread.sleep(1000); - - // Finally, delete the tenant-specific config file and verify that we fall back to the default ones - deleteFile(mainFile); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-mlb-team"); - assertNull(searchParameter); - searchParameter = SearchUtil.getSearchParameter(Patient.class, "favorite-color"); - assertNotNull(searchParameter); - - result = SearchUtil.getApplicableSearchParameters("Patient"); - assertNotNull(result); - printSearchParameters("testDynamicSearchParameters2/Patient", result); - assertEquals(37, result.size()); - } - @Test public void testGetSearchParametersWithAllResource() throws Exception { // Looking only for built-in search parameters for "Patient" versus "Resource" FHIRRequestContext.get().setTenantId("tenant6"); - List result = SearchUtil.getApplicableSearchParameters("Patient"); + List result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); printSearchParameters("testGetSearchParametersWithAllResource", result); assertEquals(33, result.size()); @@ -414,7 +294,7 @@ public void testGetSearchParametersWithAllResource() throws Exception { assertTrue(codes.contains("favorite-number")); assertTrue(codes.contains("favorite-color")); - result = SearchUtil.getApplicableSearchParameters("CarePlan"); + result = SearchUtil.getSearchParameters("CarePlan"); assertNotNull(result); printSearchParameters("testGetSearchParametersWithAllResource", result); assertEquals(27, result.size()); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersSearchUtilTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersSearchUtilTest.java index 11722bbd7f3..19a6eb1d1dd 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersSearchUtilTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersSearchUtilTest.java @@ -16,13 +16,9 @@ import java.util.Set; import java.util.stream.Collectors; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.FHIRRequestContext; -import com.ibm.fhir.exception.FHIRException; import com.ibm.fhir.model.resource.Observation; import com.ibm.fhir.model.resource.SearchParameter; import com.ibm.fhir.search.test.BaseSearchTest; @@ -34,24 +30,12 @@ public class ParametersSearchUtilTest extends BaseSearchTest { public static final boolean DEBUG = false; - @Override - @BeforeClass - public void setup() { - FHIRConfiguration.setConfigHome("src/test/resources"); - } - - @AfterMethod - public void cleanup() throws FHIRException { - // Restore the threadLocal FHIRRequestContext to the default tenant - FHIRRequestContext.get().setTenantId("default"); - } - @Test public void testGetSearchParameters1Default() throws Exception { // Simple test looking only for built-in search parameters for Observation.class. // Use default tenant id ("default") which has no Observation tenant-specific // search parameters. - List result = SearchUtil.getApplicableSearchParameters(Observation.class.getSimpleName()); + List result = SearchUtil.getSearchParameters(Observation.class.getSimpleName()); assertNotNull(result); assertFalse(result.isEmpty()); printSearchParameters("testGetSearchParameters1", result); @@ -70,12 +54,12 @@ public void testGetSearchParameters2Default() throws Exception { // parameters defined. FHIRRequestContext.get().setTenantId("default"); - List result = SearchUtil.getApplicableSearchParameters("Patient"); + List result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); printSearchParameters("testGetSearchParameters2/Patient", result); assertEquals(37, result.size()); - result = SearchUtil.getApplicableSearchParameters("Observation"); + result = SearchUtil.getSearchParameters("Observation"); assertNotNull(result); printSearchParameters("testGetSearchParameters2/Observation", result); assertEquals(44, result.size()); @@ -89,7 +73,7 @@ public void testGetSearchParameters3Tenant() throws Exception { FHIRRequestContext.get().setTenantId("tenant1"); // tenant1's filtering includes only 1 search parameter for Observation. - List result = SearchUtil.getApplicableSearchParameters("Observation"); + List result = SearchUtil.getSearchParameters("Observation"); assertNotNull(result); printSearchParameters("testGetSearchParameters3/Observation", result); @@ -105,7 +89,7 @@ public void testGetSearchParameters3Tenant() throws Exception { assertTrue(codes.contains("_lastUpdated")); assertTrue(codes.contains("_id")); - result = SearchUtil.getApplicableSearchParameters("Immunization"); + result = SearchUtil.getSearchParameters("Immunization"); assertNotNull(result); printSearchParameters("testGetSearchParameters3/Immunization", result); assertEquals(22, result.size()); @@ -116,7 +100,7 @@ public void testGetSearchParameters4Tenant() throws Exception { // Test filtering of search parameters for Device (tenant1). FHIRRequestContext.get().setTenantId("tenant1"); - List result = SearchUtil.getApplicableSearchParameters("Device"); + List result = SearchUtil.getSearchParameters("Device"); assertNotNull(result); printSearchParameters("testGetSearchParameters4/Device", result); assertEquals(8, result.size()); @@ -130,7 +114,7 @@ public void testGetSearchParameters5Tenant() throws Exception { // Test filtering of search parameters for Patient (tenant1). FHIRRequestContext.get().setTenantId("tenant1"); - List result = SearchUtil.getApplicableSearchParameters("Patient"); + List result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); printSearchParameters("testGetSearchParameters5/Patient", result); assertEquals(10, result.size()); @@ -142,7 +126,7 @@ public void testGetSearchParameters5Tenant() throws Exception { // Make sure we get all of the MedicationAdministration search parameters. // (No filtering configured for these) - result = SearchUtil.getApplicableSearchParameters("MedicationAdministration"); + result = SearchUtil.getSearchParameters("MedicationAdministration"); assertNotNull(result); printSearchParameters("testGetSearchParameters5/MedicationAdministration", result); assertEquals(19, result.size()); @@ -153,12 +137,12 @@ public void testGetSearchParameters6Default() throws Exception { // Test filtering of search parameters for Patient (default tenant). FHIRRequestContext.get().setTenantId("default"); - List result = SearchUtil.getApplicableSearchParameters("Patient"); + List result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); printSearchParameters("testGetSearchParameters6/Patient", result); assertEquals(37, result.size()); - result = SearchUtil.getApplicableSearchParameters("Device"); + result = SearchUtil.getSearchParameters("Device"); assertNotNull(result); printSearchParameters("testGetSearchParameters6/Device", result); assertEquals(20, result.size()); @@ -169,7 +153,7 @@ public void testVersionedSearchParameterFilter() throws Exception { // Test filtering of search parameters for Patient (default tenant). FHIRRequestContext.get().setTenantId("tenant4"); - List result = SearchUtil.getApplicableSearchParameters("Device"); + List result = SearchUtil.getSearchParameters("Device"); assertNotNull(result); printSearchParameters("testVersionedSearchParameterFilter/Device", result); boolean found = false; @@ -184,7 +168,7 @@ public void testVersionedSearchParameterFilter() throws Exception { FHIRRequestContext.get().setTenantId("tenant5"); - result = SearchUtil.getApplicableSearchParameters("Device"); + result = SearchUtil.getSearchParameters("Device"); assertNotNull(result); printSearchParameters("testVersionedSearchParameterFilter/Device", result); found = false; diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersUtilTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersUtilTest.java index b20bfe4f8ec..f40fefed888 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersUtilTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersUtilTest.java @@ -22,6 +22,7 @@ import java.io.Reader; import java.util.Arrays; import java.util.Map; +import java.util.Set; import org.testng.Assert; import org.testng.annotations.Test; @@ -43,22 +44,22 @@ * Tests ParametersUtil */ public class ParametersUtilTest extends BaseSearchTest { - public static final boolean DEBUG = false; + public static final boolean DEBUG = true; @Test - public void testGetBuiltInSearchParameterMap() throws IOException { + public void testGetAllSearchParameters() throws IOException { // Tests JSON - Map params = ParametersUtil.getBuiltInSearchParametersMap(); + Set params = ParametersUtil.getAllSearchParameters(); assertNotNull(params); // Intentionally the data is captured in the bytearray output stream. try (ByteArrayOutputStream outBA = new ByteArrayOutputStream(); PrintStream out = new PrintStream(outBA, true);) { ParametersUtil.print(out); Assert.assertNotNull(outBA); } - assertEquals(params.size(), 134); + assertEquals(params.size(), 1385); } - @Test(expectedExceptions = {}) + @Test public void testPopulateSearchParameterMapFromFile() throws IOException, FHIRParserException { File customSearchParams = new File("src/test/resources/config/tenant1/extension-search-parameters.json"); if (DEBUG) { @@ -94,10 +95,10 @@ public void testPrint() { } - @Test(expectedExceptions = {}) - public void testGetBuiltInSearchParameterMapByResourceType() { + @Test + public void testGetTenantSPs() { // getBuiltInSearchParameterMapByResourceType - Map result = ParametersUtil.getBuiltInSearchParametersMap(); + Map result = ParametersUtil.getTenantSPs("default"); assertNotNull(result); assertNull(result.get("Junk")); assertFalse(result.get("Observation").isEmpty()); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/BaseSearchTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/BaseSearchTest.java index c687cae96c7..8dc4b981b56 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/BaseSearchTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/BaseSearchTest.java @@ -9,6 +9,7 @@ import static org.testng.Assert.assertNotNull; import java.io.File; +import java.io.InputStream; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -16,6 +17,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.logging.LogManager; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; @@ -27,14 +29,20 @@ import com.ibm.fhir.model.type.code.ResourceType; /** - * + * A base search test with utilities for other search tests */ public abstract class BaseSearchTest { // The base uri used for all search tests. This is used to derive the incoming system url // which is required when processing search parameters public static final String BASE = "https://example.com/"; - public static final boolean DEBUG = false; + public static final boolean DEBUG = true; + + @BeforeClass + public void configureLogging() throws Exception { + final InputStream inputStream = BaseSearchTest.class.getResourceAsStream("/logging.unitTest.properties"); + LogManager.getLogManager().readConfiguration(inputStream); + } @BeforeMethod public void startMethod(Method method) { @@ -89,14 +97,13 @@ protected List getSearchParameterCodes(List spList) { * @param spList */ protected void printSearchParameters(String label, List spList) { - if (DEBUG) { + if (DEBUG && spList != null) { System.out.println("\nTest: " + label + "\nSearch Parameters:"); for (SearchParameter sp : spList) { List resources = sp.getBase(); for (ResourceType resource : resources) { System.out.println("\t" + resource.getValue() + ":" + sp.getCode().getValue()); } - } } } diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/SampleRegistryResourceProvider.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/SampleRegistryResourceProvider.java index c527e4fa189..e0d63bcc725 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/SampleRegistryResourceProvider.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/SampleRegistryResourceProvider.java @@ -60,7 +60,7 @@ public class SampleRegistryResourceProvider extends FHIRRegistryResourceProvider .build(); private List registryResources = Arrays.asList( - // Add them in descending version order so that getRegistryResource with no version returns the highest version + // Add them in descending version order so that we don't need to sort them later to have the highest version first FHIRRegistryResource.from(sp_a2), FHIRRegistryResource.from(sp_a1), @@ -74,7 +74,7 @@ public FHIRRegistryResource getRegistryResource(Class resour return registryResources.stream() .filter(rr -> rr.getResourceType() == resourceType) .filter(rr -> rr.getUrl().equals(url)) - .filter(rr -> rr.getVersion() == null || rr.getVersion().equals(version)) + .filter(rr -> rr.getVersion() == null || version == null || rr.getVersion().toString().equals(version)) .findFirst() .orElse(null); } diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/mains/Main.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/mains/Main.java index 7706d5bc01b..1228db711a3 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/mains/Main.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/mains/Main.java @@ -32,7 +32,7 @@ public class Main extends BaseSearchTest { * @throws Exception */ public static void main(String[] args) throws Exception { - for (SearchParameter parameter : SearchUtil.getApplicableSearchParameters(Resource.class.getSimpleName())) { + for (SearchParameter parameter : SearchUtil.getSearchParameters(Resource.class.getSimpleName())) { String code = parameter.getCode().getValue(); String type = parameter.getType().getValue(); String description = parameter.getDescription().getValue(); @@ -43,7 +43,7 @@ public static void main(String[] args) throws Exception { System.out.println("name: " + code + ", type: " + type + ", description: " + description + ", xpath: " + xpath); } - for (SearchParameter parameter : SearchUtil.getApplicableSearchParameters(Patient.class.getSimpleName())) { + for (SearchParameter parameter : SearchUtil.getSearchParameters(Patient.class.getSimpleName())) { String code = parameter.getCode().getValue(); String type = parameter.getType().getValue(); String description = parameter.getDescription().getValue(); diff --git a/fhir-search/src/test/resources/logging.unitTest.properties b/fhir-search/src/test/resources/logging.unitTest.properties new file mode 100644 index 00000000000..dcb0849ea70 --- /dev/null +++ b/fhir-search/src/test/resources/logging.unitTest.properties @@ -0,0 +1,51 @@ +# ======================================================================== +# (C) Copyright IBM Corp. 2016, 2021 +# +# SPDX-License-Identifier: Apache-2.0 +# ======================================================================== +# +# Default Logging Configuration File +# +# You can use a different file by specifying a filename +# with the java.util.logging.config.file system property. +# For example java -Djava.util.logging.config.file=myfile +############################################################ + +############################################################ +# Global properties +############################################################ + +# "handlers" specifies a comma separated list of log Handler +# classes. These handlers will be installed during VM startup. +# Note that these classes must be on the system classpath. +# By default we only configure a ConsoleHandler. +handlers= java.util.logging.ConsoleHandler + +# Default global logging level. +# This specifies which kinds of events are logged across +# all loggers. For any given facility this global level +# can be overriden by a facility specific level +# Note that the ConsoleHandler also has a separate level +# setting to limit messages printed to the console. +.level= INFO + +############################################################ +# Handler specific properties. +# Describes specific configuration info for Handlers. +############################################################ + +# Limit the messages that are printed to the console +java.util.logging.ConsoleHandler.level = FINE +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter + +# Example to customize the SimpleFormatter output format +# to print one-line log message like this: +# : [] +#java.util.logging.SimpleFormatter.format=%5$s [%1$tc]%n + +############################################################ +# Facility specific properties. +# Provides extra control for each logger. +############################################################ + +com.ibm.fhir.search.level = FINE \ No newline at end of file