diff --git a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRRequestContext.java b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRRequestContext.java index dbb2c7ab549..f7f6d8888a7 100644 --- a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRRequestContext.java +++ b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRRequestContext.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -177,6 +177,8 @@ public static void set(FHIRRequestContext context) { /** * Returns the FHIRRequestContext on the current thread. + * If it doesn't exist yet, this method will create a default instance + * and associate that with the current thread before returning it. */ public static FHIRRequestContext get() { FHIRRequestContext result = contexts.get(); diff --git a/fhir-model/src/test/java/com/ibm/fhir/model/test/TestUtil.java b/fhir-model/src/test/java/com/ibm/fhir/model/test/TestUtil.java index c727c0a98bf..0c299b7540f 100644 --- a/fhir-model/src/test/java/com/ibm/fhir/model/test/TestUtil.java +++ b/fhir-model/src/test/java/com/ibm/fhir/model/test/TestUtil.java @@ -1,12 +1,11 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ package com.ibm.fhir.model.test; -import static com.ibm.fhir.model.type.String.string; import static org.testng.AssertJUnit.fail; import java.io.File; @@ -117,12 +116,29 @@ public static JsonObject getRequestJsonObject(String method, String url) { * the specified patient via a subject attribute. */ public static Observation buildPatientObservation(String patientId, String fileName) throws Exception { - // TODO review Reference id Observation observation = readLocalResource(fileName); observation = observation .toBuilder() - .subject(Reference.builder().reference(string("Patient/" + patientId)).build()) + .subject(Reference.builder().reference("Patient/" + patientId).build()) + .build(); + return observation; + } + + /** + * Loads an Observation resource from the specified file, then associates it with + * + */ + public static Observation buildPatientObservation(String patientId, String practitionerId, String fileName) throws Exception { + Observation observation = readLocalResource(fileName); + + observation = observation + .toBuilder() + .subject(Reference.builder().reference("Patient/" + patientId).build()) + .performer(Reference.builder().reference("Practitioner/" + practitionerId).build()) .build(); return observation; } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/ExtractedParameterValue.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/ExtractedParameterValue.java index ae836291ee0..d8969a40cdc 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/ExtractedParameterValue.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/ExtractedParameterValue.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,6 +26,9 @@ public abstract class ExtractedParameterValue implements Comparable T copyAndSetResourceMetaFields(T resource, String logicalId, int newVersionNumber, Instant lastUpdated) { - return FHIRPersistenceUtil.copyAndSetResourceMetaFields(resource, logicalId, newVersionNumber, lastUpdated); - } - /** * Convenience method to construct a new instance of the {@link ResourceDAO} * @param connection the connection to the database for the DAO to use @@ -1601,8 +1587,10 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, String version; String type; String expression; + boolean isForStoring; - List allParameters = new ArrayList<>(); + // LinkedList because we don't need random access, we might remove from it later, and we want this fast + List allParameters = new LinkedList<>(); String parameterHashB64 = null; try { @@ -1617,6 +1605,9 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, version = sp.getVersion() != null ? sp.getVersion().getValue(): null; final boolean wholeSystemParam = isWholeSystem(sp); + String doNotStoreExtVal = FHIRUtil.getExtensionStringValue(sp, SearchConstants.DO_NOT_STORE_EXT_URL); + isForStoring = (doNotStoreExtVal == null) || "false".equals(doNotStoreExtVal); + // As not to inject any other special handling logic, this is a simple inline check to see if // _id or _lastUpdated are used, and ignore those extracted values. if (SPECIAL_HANDLING.contains(code)) { @@ -1665,9 +1656,8 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, continue; } - // Alternative: consider pulling the search parameter from the FHIRRegistry instead so we can use versioned references. - // Of course, that would require adding extension-search-params to the Registry which would require the Registry to be tenant-aware. - // SearchParameter compSP = FHIRRegistry.getInstance().getResource(component.getDefinition().getValue(), SearchParameter.class); + // Alternatively, we could pull the search parameter from the FHIRRegistry so we can use versioned references. + // However, that would bypass search parameter filtering and so we favor the SeachUtil method here instead. SearchParameter compSP = SearchUtil.getSearchParameter(p.getResourceType(), component.getDefinition()); JDBCParameterBuildingVisitor parameterBuilder = new JDBCParameterBuildingVisitor(p.getResourceType(), compSP); FHIRPathNode node = nodes.iterator().next(); @@ -1757,6 +1747,7 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, if (components.size() == p.getComponent().size()) { // only add the parameter if all of the components are present and accounted for + p.setForStoring(isForStoring); allParameters.add(p); } } @@ -1779,6 +1770,7 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, if (wholeSystemParam) { p.setWholeSystem(true); } + p.setForStoring(isForStoring); allParameters.add(p); if (log.isLoggable(Level.FINE)) { log.fine("Extracted Parameter '" + p.getName() + "' from Resource."); @@ -1814,6 +1806,7 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, if (wholeSystemParam) { p.setWholeSystem(true); } + p.setForStoring(isForStoring); allParameters.add(p); if (log.isLoggable(Level.FINE)) { log.fine("Extracted Parameter '" + p.getName() + "' from Resource."); @@ -1825,7 +1818,7 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, // Augment the extracted parameter list with special values we use to represent compartment relationships. // These references are stored as tokens and are used by the search query builder // for compartment-based searches - addCompartmentParams(allParameters, fhirResource); + addCompartmentParams(allParameters, fhirResource.getClass().getSimpleName()); // If this is a definitional resource, augment the extracted parameter list with a composite // parameter that will be used for canonical searches. It will contain the url and version @@ -1833,6 +1826,12 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, addCanonicalCompositeParam(allParameters); } + // Remove parameters that aren't to be stored + boolean anyRemoved = allParameters.removeIf(value -> !value.isForStoring()); + if (anyRemoved) { + log.warning("The set of extracted parameters values unexpectedly contained values " + + "that weren't for storing; these have been removed"); + } // Generate the hash which is used to quickly determine whether the extracted parameters // are different than the extracted parameters that currently exist in the database. @@ -1886,39 +1885,67 @@ private boolean isWholeSystem(SearchParameter sp) { } /** - * Augment the given allParameters list with ibm-internal parameters that represent relationships - * between the fhirResource to its compartments. These parameter values are subsequently used + * Augment the given allParameters list with ibm-internal parameters that represent the relationship + * between the fhirResource and its compartments. These parameter values are subsequently used * to improve the performance of compartment-based FHIR search queries. See * {@link CompartmentUtil#makeCompartmentParamName(String)} for details on how the * parameter name is composed for each relationship. * @param allParameters + * @param resourceType */ - protected void addCompartmentParams(List allParameters, Resource fhirResource) throws FHIRSearchException { - final String resourceType = fhirResource.getClass().getSimpleName(); + protected void addCompartmentParams(List allParameters, String resourceType) { if (log.isLoggable(Level.FINE)) { log.fine("Processing compartment parameters for resourceType: " + resourceType); } + Map> compartmentRefParams = CompartmentUtil.getCompartmentParamsForResourceType(resourceType); - Map> compartmentMap = SearchUtil.extractCompartmentParameterValues(fhirResource, compartmentRefParams); + Map> collectedEPVByCode = allParameters.stream() + .collect(Collectors.groupingBy(epv -> epv.getName())); - for (Map.Entry> entry: compartmentMap.entrySet()) { - final String compartmentName = entry.getKey(); - final String parameterName = CompartmentUtil.makeCompartmentParamName(compartmentName); + for (Entry> entry : compartmentRefParams.entrySet()) { + String param = entry.getKey(); + Set compartments = entry.getValue(); - // Create a reference parameter value for each CompartmentReference extracted from the resource - for (CompartmentReference compartmentRef: entry.getValue()) { - ReferenceParmVal pv = new ReferenceParmVal(); - pv.setName(parameterName); - pv.setResourceType(resourceType); + List collectedEPV = collectedEPVByCode.get(param); + // If the parameter has no corresponding extracted values, log and continue + if (collectedEPV == null) { + if (log.isLoggable(Level.FINE)) { + log.warning("Compartment inclusion param " + param + " has no value"); + } + continue; + } - // ReferenceType doesn't really matter here, but LITERAL_RELATIVE is appropriate - ReferenceValue rv = new ReferenceValue(compartmentName, compartmentRef.getReferenceResourceValue(), ReferenceType.LITERAL_RELATIVE, null); - pv.setRefValue(rv); + for (ExtractedParameterValue epv : collectedEPV) { + if (!(epv instanceof ReferenceParmVal)) { + log.warning("Skipping compartment inclusion param " + param + "; expected ReferenceParmVal but found " + + epv.getClass().getSimpleName()); + continue; + } - if (log.isLoggable(Level.FINE)) { - log.fine("Adding compartment reference parameter: [" + resourceType + "] "+ parameterName + " = " + rv.getTargetResourceType() + "/" + rv.getValue()); + ReferenceValue rv = ((ReferenceParmVal) epv).getRefValue(); + if (rv != null && rv.getType() == ReferenceType.LITERAL_RELATIVE + && compartments.contains(rv.getTargetResourceType())) { + String internalCompartmentParamName = CompartmentUtil.makeCompartmentParamName(rv.getTargetResourceType()); + + if (epv.isForStoring()) { + // create a copy of the extracted parameter value but with the new internal compartment parameter name + ReferenceParmVal pv = new ReferenceParmVal(); + pv.setName(internalCompartmentParamName); + pv.setResourceType(resourceType); + pv.setRefValue(rv); + + if (log.isLoggable(Level.FINE)) { + log.fine("Adding compartment reference parameter: [" + resourceType + "] " + + internalCompartmentParamName + " = " + rv.getTargetResourceType() + "/" + rv.getValue()); + } + allParameters.add(pv); + } else { + // since the extracted parameter value isn't going to be stored, + // just rename it with our internal compartment param name and mark that for storing + epv.setName(internalCompartmentParamName); + epv.setForStoring(true); + } } - allParameters.add(pv); } } } 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 81c6be24193..35b105af567 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 @@ -18,6 +18,7 @@ import com.ibm.fhir.core.FHIRConstants; import com.ibm.fhir.model.type.Code; import com.ibm.fhir.model.type.Coding; +import com.ibm.fhir.model.type.Extension; import com.ibm.fhir.model.type.Uri; import com.ibm.fhir.search.exception.SearchExceptionUtil; @@ -102,6 +103,11 @@ private SearchConstants() { public static final String VERSION = "version"; public static final String IMPLICIT_SYSTEM_EXT_URL = FHIRConstants.EXT_BASE + "implicit-system"; + public static final String DO_NOT_STORE_EXT_URL = FHIRConstants.EXT_BASE + "do-not-store"; + public static final Extension DO_NOT_STORE_EXT = Extension.builder() + .url(SearchConstants.DO_NOT_STORE_EXT_URL) + .value(true) + .build(); // Extracted search parameter suffix for :identifier modifier public static final String IDENTIFIER_MODIFIER_SUFFIX = ":identifier"; 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 541f5d56af7..26741e38946 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,6 +30,8 @@ public class ParametersMap { private final Map codeMap; private final Map canonicalMap; + private final Map inclusionParamMap; + /** * Construct a ParametersMap from a Bundle of SearchParameter */ @@ -37,6 +39,10 @@ public ParametersMap() { // LinkedHashMaps to preserve insertion order codeMap = new LinkedHashMap<>(); canonicalMap = new LinkedHashMap<>(); + + // Inclusion parameters are stored separately because they may be internal-only + // i.e. not externally searchable except through compartment search + inclusionParamMap = new LinkedHashMap<>(); } /** @@ -82,6 +88,22 @@ public void insert(String code, SearchParameter parameter) { } } + /** + * @param code + * @param parameter + * @implSpec Any existing parameters will be replaced and a warning will be logged; last insert wins + */ + public void insertInclusionParam(String code, SearchParameter parameter) { + Objects.requireNonNull(code, "cannot insert a null code"); + Objects.requireNonNull(parameter, "cannot insert a null parameter"); + + if (inclusionParamMap.containsKey(code)) { + SearchParameter previous = inclusionParamMap.get(code); + logParamConflict("inclusion criteria '" + code + "'", parameter, ParametersUtil.getCanonicalUrl(parameter), previous); + } + inclusionParamMap.put(code, parameter); + } + private void logParamConflict(String distinguisher, SearchParameter parameter, String canonical, SearchParameter previous) { if (previous.getExpression().equals(parameter.getExpression())) { if (log.isLoggable(Level.FINE)) { @@ -99,24 +121,50 @@ private void logParamConflict(String distinguisher, SearchParameter parameter, S } } - public void insertAll(ParametersMap map) { - for (Entry entry : map.codeEntries()) { - insert(entry.getKey(), entry.getValue()); - } - } - + /** + * Get the set of SearchParameter codes that have been added to this map. + * @return + * @implSpec This does not include any compartment inclusion criteria codes added + * via {@link #insertInclusionParam(String, SearchParameter)}; + * use {@link com.ibm.fhir.search.compartment.CompartmentUtil} for those. + */ public Set getCodes() { return codeMap.keySet(); } + /** + * Look up a search parameter that has been added to this map by its code. + * @param searchParameterCode + * @return null if it doesn't exist + */ public SearchParameter lookupByCode(String searchParameterCode) { return codeMap.get(searchParameterCode); } + /** + * Look up a search parameter that has been added to this map by its canonical URL. + * @param searchParameterCanonical + * @return null if it doesn't exist + */ public SearchParameter lookupByCanonical(String searchParameterCanonical) { return canonicalMap.get(searchParameterCanonical); } + /** + * Get a SearchParameter that has been added to this map as an inclusion parameter by its code. + * @param searchParameterCode + * @return null if it doesn't exist + */ + public SearchParameter getInclusionParam(String searchParameterCode) { + return inclusionParamMap.get(searchParameterCode); + } + + /** + * @return the set of search parameters added to this map + * @implSpec this set does not include SearchParameters added to the map via + * {@link ParametersMap#insertInclusionParam(String, SearchParameter)}; + * those can be obtained from {@link ParametersMap#inlcusionValues()} + */ public Collection values() { // use List to preserve order return Collections.unmodifiableList(canonicalMap.entrySet().stream() @@ -125,18 +173,24 @@ public Collection values() { .collect(Collectors.toList())); } - public boolean isEmpty() { - return codeMap.isEmpty(); - } - - public int size() { - return codeMap.size(); - } - + /** + * @return the set of search parameters in the map, indexed by code + * @implSpec this set does not include SearchParameters added to the map via + * {@link ParametersMap#insertInclusionParam(String, SearchParameter)} + */ public Set> codeEntries() { return Collections.unmodifiableSet(codeMap.entrySet()); } + /** + * @return the set of compartment inclusion criteria parameter values + * @implSpec this set may overlap with the set of "normal" SearchParameters + * that can be obtained from {@link #values()} + */ + public Collection inclusionValues() { + return inclusionParamMap.values(); + } + /** * @implSpec Note that versioned search parameters will be listed twice; * once with their version and once without 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 78aecc0fd7b..7a70e802dbd 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,6 +29,7 @@ import com.ibm.fhir.model.util.ModelSupport; import com.ibm.fhir.registry.FHIRRegistry; import com.ibm.fhir.search.SearchConstants; +import com.ibm.fhir.search.compartment.CompartmentUtil; import com.ibm.fhir.search.exception.SearchExceptionUtil; /** @@ -60,9 +61,10 @@ public final class ParametersUtil { // Logging: public static final String LOG_PARAMETERS = "Parameter is loaded -> %s"; - public static final String LOG_HEADER = "BASE:RESOURCE_NAME:SearchParameter"; - public static final String LOG_SIZE = "Size: %s"; - private static final String LOG_OUTPUT = "%s|%s|%s"; + public static final String LOG_HEADER = "ResourceType | SearchParameter.code | SearchParameter.expression\n" + + "'*' denotes compartment inclusion criteria parameters"; + public static final String LOG_SIZE = "Number of resource types: %s"; + private static final String LOG_OUTPUT = "%s | %s | %s"; private static final String LEFT = "["; private static final String RIGHT = "]"; @@ -129,7 +131,7 @@ private static Map> buildSearchParameterMaps( } } - return Collections.unmodifiableMap(result); + return result; } private static Map computeTenantSPs(PropertyGroup rsrcsGroup) throws Exception { @@ -175,7 +177,7 @@ private static Map computeTenantSPs(PropertyGroup rsrcsGr } } - addWildcardParams(paramMapsByType, resourceTypesWithWildcardParams, configuredCodes); + addWildcardAndCompartmentParams(paramMapsByType, resourceTypesWithWildcardParams, configuredCodes); return Collections.unmodifiableMap(paramMapsByType); } @@ -192,6 +194,9 @@ private static ParametersMap getExplicitParams(Set resourceTypesWithWild if (spGroup != null) { List spEntries = spGroup.getProperties(); if (spEntries != null && !spEntries.isEmpty()) { + + Map> compartmentParamToCompartment = CompartmentUtil.getCompartmentParamsForResourceType(resourceType); + for (PropertyEntry spEntry : spEntries) { String code = spEntry.getName(); if (SearchConstants.WILDCARD.equals(code)) { @@ -201,8 +206,14 @@ private static ParametersMap getExplicitParams(Set resourceTypesWithWild .getResource((String)spEntry.getValue(), SearchParameter.class); if (sp != null) { paramMap.insert(code, sp); + + // If this param is an inclusion criteria for one or more compartments + if (compartmentParamToCompartment.containsKey(code)) { + paramMap.insertInclusionParam(code, sp); + } } else { - log.warning("Search parameter '" + code + "' with the configured url '" + spEntry.getValue() + "' for resourceType '" + resourceType + "' could not be found."); + log.warning("Search parameter '" + code + "' with the configured url '" + spEntry.getValue() + + "' for resourceType '" + resourceType + "' could not be found."); } } } @@ -215,10 +226,11 @@ private static ParametersMap getExplicitParams(Set resourceTypesWithWild return paramMap; } - private static void addWildcardParams(Map paramMapsByType, + private static void addWildcardAndCompartmentParams(Map paramMapsByType, Set resourceTypesWithWildcardParams, Map> configuredCodes) { for (SearchParameter sp : getAllSearchParameters()) { + String code = sp.getCode().getValue(); // For each resource type this search parameter applies to for (ResourceType resourceType : sp.getBase()) { @@ -232,17 +244,38 @@ private static void addWildcardParams(Map paramMapsByType // Only add it if the code wasn't explicitly configured in fhir-server-config Set configuredCodesForType = configuredCodes.get(resourceType.getValue()); - if (configuredCodesForType != null && configuredCodesForType.contains(sp.getCode().getValue())) { + if (configuredCodesForType != null && configuredCodesForType.contains(code)) { if (log.isLoggable(Level.FINE)) { String canonical = getCanonicalUrl(sp); log.fine("Skipping search parameter '" + canonical + "' because code '" + sp.getCode().getValue() + "' is already configured."); } } else { - paramMap.insert(sp.getCode().getValue(), sp); + paramMap.insert(code, sp); } } + // If this param is an inclusion criteria for one or more compartments + Map> compartmentParamToCompartment = CompartmentUtil.getCompartmentParamsForResourceType(resourceType.getValue()); + if (compartmentParamToCompartment.containsKey(code)) { + ParametersMap paramMap = paramMapsByType.get(resourceType.getValue()); + if (paramMap == null) { + paramMap = new ParametersMap(); + paramMapsByType.put(resourceType.getValue(), paramMap); + } + + // Only add it if the code wasn't explicitly configured in fhir-server-config + Set configuredCodesForType = configuredCodes.get(resourceType.getValue()); + if (configuredCodesForType != null && configuredCodesForType.contains(code)) { + if (log.isLoggable(Level.FINE)) { + String canonical = getCanonicalUrl(sp); + log.fine("Skipping compartment inclusion parameter '" + canonical + "' because code '" + + sp.getCode().getValue() + "' is already configured."); + } + } else { + paramMap.insertInclusionParam(code, sp); + } + } } } } @@ -268,8 +301,17 @@ public static Map getTenantSPs(String tenantId) { if (searchParameters.containsKey(tenantId)) { return searchParameters.get(tenantId); } else { - log.warning("No search parameter configuration was loaded for tenant " + tenantId); - return new HashMap<>(); + log.warning("No search parameter configuration was loaded for tenant " + tenantId + "; computing now"); + try { + PropertyGroup root = FHIRConfiguration.getInstance().loadConfigurationForTenant(tenantId); + PropertyGroup rsrcsGroup = root == null ? null : root.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES); + Map tenantSPs = computeTenantSPs(rsrcsGroup); + searchParameters.put(tenantId, tenantSPs); + return tenantSPs; + } catch (Exception e) { + log.log(Level.SEVERE, "Error while computing search parameters for tenant " + tenantId + "; returning empty", e); + return new HashMap<>(); + } } } @@ -331,13 +373,20 @@ private static void print(PrintStream out, Map searchPara out.println(String.format(LOG_SIZE, keys.size())); for (String base : keys) { ParametersMap tmp = searchParamsMap.get(base); - for(SearchParameter param : tmp.values()) { + for (SearchParameter param : tmp.values()) { String expression = MISSING_EXPRESSION; if (param.getExpression() != null) { expression = param.getExpression().getValue(); } out.println(String.format(LOG_OUTPUT, base, param.getCode().getValue(), expression)); } + for (SearchParameter param : tmp.inclusionValues()) { + String expression = MISSING_EXPRESSION; + if (param.getExpression() != null) { + expression = param.getExpression().getValue(); + } + out.println(String.format(LOG_OUTPUT, "*" + base, param.getCode().getValue(), expression)); + } out.println(SearchConstants.LOG_BOUNDARY); } out.println(SearchConstants.LOG_BOUNDARY); diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/reference/value/CompartmentReference.java b/fhir-search/src/main/java/com/ibm/fhir/search/reference/value/CompartmentReference.java deleted file mode 100644 index de214e59289..00000000000 --- a/fhir-search/src/main/java/com/ibm/fhir/search/reference/value/CompartmentReference.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * (C) Copyright IBM Corp. 2020 - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.ibm.fhir.search.reference.value; - -import java.util.Objects; - -/** - * Represents a reference to a resource compartment extracted by SearchUtil - */ -public class CompartmentReference { - - private final String parameterName; - - private final String referenceResourceType; - - private final String referenceResourceValue; - - /** - * Public constructor - * @param parameterName - * @param referenceResourceType - * @param referenceResourceValue - */ - public CompartmentReference(String parameterName, String referenceResourceType, String referenceResourceValue) { - Objects.requireNonNull(parameterName, "parameterName"); - Objects.requireNonNull(referenceResourceType, "referenceResourceType"); - Objects.requireNonNull(referenceResourceValue, "referenceResourceValue"); - this.parameterName = parameterName; - this.referenceResourceType = referenceResourceType; - this.referenceResourceValue = referenceResourceValue; - } - - @Override - public int hashCode() { - int prime = 31; - return parameterName.hashCode() + prime * (referenceResourceType.hashCode() + prime * referenceResourceValue.hashCode()); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof CompartmentReference) { - CompartmentReference that = (CompartmentReference)obj; - return this.parameterName.equals(that.parameterName) - && this.referenceResourceType.equals(that.referenceResourceType) - && this.referenceResourceValue.equals(that.referenceResourceValue); - } else { - return false; - } - } - - - /** - * @return the parameterName - */ - public String getParameterName() { - return parameterName; - } - - - /** - * @return the referenceResourceType - */ - public String getReferenceResourceType() { - return referenceResourceType; - } - - - /** - * @return the referenceResourceValue - */ - public String getReferenceResourceValue() { - return referenceResourceValue; - } -} 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 130da8f15cd..61a266376c4 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 @@ -6,8 +6,6 @@ package com.ibm.fhir.search.util; -import static com.ibm.fhir.model.util.ModelSupport.FHIR_STRING; - import java.math.BigDecimal; import java.net.URISyntaxException; import java.net.URLEncoder; @@ -43,9 +41,7 @@ import com.ibm.fhir.model.resource.ValueSet; import com.ibm.fhir.model.type.Canonical; import com.ibm.fhir.model.type.Code; -import com.ibm.fhir.model.type.Element; import com.ibm.fhir.model.type.Extension; -import com.ibm.fhir.model.type.Reference; import com.ibm.fhir.model.type.Uri; import com.ibm.fhir.model.type.code.IssueSeverity; import com.ibm.fhir.model.type.code.IssueType; @@ -77,7 +73,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.reference.value.CompartmentReference; import com.ibm.fhir.search.sort.Sort; import com.ibm.fhir.search.uri.UriBuilder; import com.ibm.fhir.search.util.ReferenceValue.ReferenceType; @@ -319,6 +314,17 @@ public static Map> extractParameterValues(Re Map parameters = getSearchParameters(resourceType.getSimpleName()); + for (String inclusionParamName : CompartmentUtil.getCompartmentParamsForResourceType(resourceType.getSimpleName()).keySet()) { + if (!COMPARTMENT_PARM_DEF.equals(inclusionParamName) && !parameters.containsKey(inclusionParamName)) { + String tenantId = FHIRRequestContext.get().getTenantId(); + ParametersMap parametersMap = ParametersUtil.getTenantSPs(tenantId).get(resourceType.getSimpleName()); + SearchParameter inclusionParam = parametersMap.getInclusionParam(inclusionParamName).toBuilder() + .extension(SearchConstants.DO_NOT_STORE_EXT) + .build(); + parameters.put(inclusionParamName, inclusionParam); + } + } + for (Entry parameterEntry : parameters.entrySet()) { String code = parameterEntry.getKey(); SearchParameter parameter = parameterEntry.getValue(); @@ -2173,104 +2179,6 @@ private static void manageException(String message, IssueType issueType, FHIRSea } } - /** - * Extracts the parameter values defining compartment membership. - * @param fhirResource - * @param compartmentRefParams a map of parameter names to a set of compartment names (resource types) - * @return a map of compartment name to a set of unique compartment reference values - */ - public static Map> extractCompartmentParameterValues(Resource fhirResource, - Map> compartmentRefParams) throws FHIRSearchException { - final Map> result = new HashMap<>(); - final String resourceType = fhirResource.getClass().getSimpleName(); - - // TODO, probably should use a Bundle.Entry value here if we are processing a bundle - final String baseUrl = ReferenceUtil.getBaseUrl(null); - - try { - EvaluationContext resourceContext = new FHIRPathEvaluator.EvaluationContext(fhirResource); - - // Extract any references we find matching parameters representing compartment membership. - // For example CareTeam.participant can be used to refer to a Patient or RelatedPerson resource: - // "participant": { "reference": "Patient/abc123" } - // "participant": { "reference": "RelatedPerson/abc456" } - for (Map.Entry> paramEntry : compartmentRefParams.entrySet()) { - final String searchParm = paramEntry.getKey(); - - // Ignore {def} which is used in the compartment definition where - // no other search parm is given (e.g. Encounter->Encounter). - if (!COMPARTMENT_PARM_DEF.equals(searchParm)) { - SearchParameter sp = SearchUtil.getSearchParameter(resourceType, searchParm); - if (sp != null && sp.getExpression() != null) { - String expression = sp.getExpression().getValue(); - - if (log.isLoggable(Level.FINE)) { - log.fine("searchParam = [" + resourceType + "] '" + searchParm + "'; expression = '" + expression + "'"); - } - Collection nodes = FHIRPathEvaluator.evaluator().evaluate(resourceContext, expression); - - if (log.isLoggable(Level.FINEST)) { - log.finest("Expression [" + expression + "], parameter-code [" - + searchParm + "], size [" + nodes.size() + "]"); - } - - for (FHIRPathNode node : nodes) { - String compartmentName = null; - String compartmentId = null; - - Element element = node.asElementNode().element(); - if (element.is(Reference.class)) { - Reference reference = element.as(Reference.class); - ReferenceValue rv = ReferenceUtil.createReferenceValueFrom(reference, baseUrl); - if (rv.getType() == ReferenceType.DISPLAY_ONLY || rv.getType() == ReferenceType.INVALID) { - if (log.isLoggable(Level.FINE)) { - log.fine("Skipping reference of type " + rv.getType()); - } - continue; - } - compartmentName = rv.getTargetResourceType(); - compartmentId = rv.getValue(); - - // Check that the target resource type of the reference matches one of the - // target resource types in the compartment definition. - if (!paramEntry.getValue().contains(compartmentName)) { - if (log.isLoggable(Level.FINE)) { - String refVal = (reference.getReference() == null) ? null : reference.getReference().getValue(); - log.fine("Skipping reference with value " + refVal + ";" - + " target resource type does not match any of the allowed compartment types: " + paramEntry); - } - continue; - } - } else if (element.is(FHIR_STRING)) { - if (paramEntry.getValue().size() != 1) { - log.warning("CompartmentDefinition inclusion criteria must be of type Reference unless they have 1 and only 1 resource target"); - continue; - } - compartmentName = paramEntry.getValue().iterator().next(); - compartmentId = element.as(FHIR_STRING).getValue(); - } - - // Add this reference to the set of references we're collecting for each compartment - CompartmentReference cref = new CompartmentReference(searchParm, compartmentName, compartmentId); - Set references = result.computeIfAbsent(compartmentName, k -> new HashSet<>()); - references.add(cref); - } - } else if (!useStoredCompartmentParam()) { - log.warning("Compartment parameter not found: [" + resourceType + "] '" + searchParm + "'. " - + "This will stop compartment searches from working correctly."); - } - - } - } - } catch (Exception e) { - final String msg = "Unexpected exception extracting compartment references " - + " for resource type '" + resourceType + "'"; - log.log(Level.WARNING, msg, e); - throw SearchExceptionUtil.buildNewInvalidSearchException(msg); - } - return result; - } - /** * Throw an exception if the specified search parameter is null. * diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingBoxTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingBoxTest.java index abbb260b0b0..ca8c8b066e4 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingBoxTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingBoxTest.java @@ -7,6 +7,7 @@ package com.ibm.fhir.search.location; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import java.lang.reflect.Method; @@ -172,6 +173,7 @@ public void testLocationBoundaryPositionsFromParameters_tenant_near() throws Exc NearLocationHandler handler = new NearLocationHandler(); List bounding = handler.generateLocationPositionsFromParameters(ctx.getSearchParameters()); assertNotNull(bounding); + assertFalse(bounding.isEmpty(), "generateLocationPositionsFromParameters came back empty for tenant " + FHIRRequestContext.get().getTenantId()); BoundingBox boundingBox = (BoundingBox) bounding.get(0); // At the low latitudes it's going to cover most of the area. 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 0235d92c09c..91e365c4713 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -37,7 +37,7 @@ public class MultiTenantSearchParameterTest extends BaseSearchTest { @AfterMethod public void cleanup() throws FHIRException { // Restore the threadLocal FHIRRequestContext to the default tenant - FHIRRequestContext.get().setTenantId("default"); + FHIRRequestContext.remove(); } @Test @@ -68,8 +68,8 @@ public void testGetApplicableSearchParameters1() throws Exception { public void testGetApplicableSearchParameters3() throws Exception { // Looking for built-in and tenant-specific search parameters for "Patient" and "Observation". - // Use the default tenant since it has some Patient search parameters defined. - FHIRRequestContext.get().setTenantId("default"); + // Use the extended tenant since it has some Patient search parameters defined. + FHIRRequestContext.get().setTenantId("extended"); Map result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); @@ -141,8 +141,8 @@ public void testGetApplicableSearchParameters6() throws Exception { @Test public void testGetApplicableSearchParameters7() throws Exception { - // Test filtering of search parameters for Patient (default tenant). - FHIRRequestContext.get().setTenantId("default"); + // Test filtering of search parameters for Patient (extended tenant). + FHIRRequestContext.get().setTenantId("extended"); Map result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersMapTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersMapTest.java index 4d652bb54bf..3d19b628d99 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersMapTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/ParametersMapTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,114 +19,155 @@ import com.ibm.fhir.model.type.code.ResourceType; import com.ibm.fhir.model.type.code.SearchParamType; +/** + * ParametersMap tests + */ public class ParametersMapTest { - SearchParameter sp_a1 = SearchParameter.builder() - .url(Uri.of("http://ibm.com/fhir/test/sp_a1")) - .status(PublicationStatus.ACTIVE) - .name(string("a")) - .description(Markdown.of("First param with code 'a'")) - .base(ResourceType.RESOURCE) - .type(SearchParamType.STRING) - .code(Code.of("a")) - .expression(string("extension.value as String")) - .build(); - SearchParameter sp_a2 = SearchParameter.builder() - .url(Uri.of("http://ibm.com/fhir/test/sp_a2")) - .status(PublicationStatus.ACTIVE) - .name(string("a")) - .description(Markdown.of("Second param with code 'a'")) - .base(ResourceType.RESOURCE) - .type(SearchParamType.STRING) - .code(Code.of("a")) - .expression(string("extension.value as String")) - .build(); - SearchParameter sp_b = SearchParameter.builder() - .url(Uri.of("http://ibm.com/fhir/test/sp_b")) - .status(PublicationStatus.ACTIVE) - .name(string("b")) - .base(ResourceType.RESOURCE) - .type(SearchParamType.STRING) - .description(Markdown.of("Param with code 'b'")) - .code(Code.of("b")) - .expression(string("extension.value as String")) - .build(); - SearchParameter sp_b1 = SearchParameter.builder() - .url(Uri.of("http://ibm.com/fhir/test/sp_b")) - .version("1") - .status(PublicationStatus.ACTIVE) - .name(string("b")) - .base(ResourceType.RESOURCE) - .type(SearchParamType.STRING) - .description(Markdown.of("Param with code 'b'")) - .code(Code.of("b")) - .expression(string("extension.value as String")) - .build(); - SearchParameter sp_b2 = SearchParameter.builder() - .url(Uri.of("http://ibm.com/fhir/test/sp_b")) - .version("2") - .status(PublicationStatus.ACTIVE) - .name(string("b")) - .base(ResourceType.RESOURCE) - .type(SearchParamType.STRING) - .description(Markdown.of("Param with code 'b'")) - .code(Code.of("b")) - .expression(string("extension.value as String")) - .build(); - - @Test - public void testParametersMap() { - ParametersMap pm = new ParametersMap(); - pm.insert("a", sp_a1); - pm.insert("a", sp_a2); - pm.insert("b", sp_b); - pm.insert("b", sp_b1); - pm.insert("b", sp_b2); - - assertEquals(pm.lookupByCode("a"), sp_a2); - assertEquals(pm.lookupByCode("b"), sp_b2); - - assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_a1"), sp_a1); - assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_a2"), sp_a2); - assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b"), sp_b2); - assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b|1"), sp_b1); - assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b|2"), sp_b2); - - assertTrue(pm.codeEntries().size() == 2); - assertTrue(pm.canonicalEntries().size() == 5); // versionless ones for a1, a2, and b2; versioned ones for b1 and b2 - } - - /** - * Adding the same parameter under two different codes should be supported - */ - @Test - public void testParametersMap_duplicateUrl() { - ParametersMap pm = new ParametersMap(); - pm.insert("b", sp_b); - pm.insert("b2", sp_b); - - assertEquals(pm.lookupByCode("b"), sp_b); - assertEquals(pm.lookupByCode("b2"), sp_b); - - assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b"), sp_b); - - assertTrue(pm.codeEntries().size() == 2); - assertTrue(pm.canonicalEntries().size() == 1); - } - - /** - * Adding the same parameter twice should be the same as adding it once - */ - @Test - public void testParametersMap_duplicate() { - ParametersMap pm = new ParametersMap(); - pm.insert("b", sp_b); - pm.insert("b", sp_b); - - assertEquals(pm.lookupByCode("b"), sp_b); - - assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b"), sp_b); - - assertTrue(pm.codeEntries().size() == 1); - assertTrue(pm.canonicalEntries().size() == 1); - } + SearchParameter sp_a1 = SearchParameter.builder() + .url(Uri.of("http://ibm.com/fhir/test/sp_a1")) + .status(PublicationStatus.ACTIVE) + .name(string("a")) + .description(Markdown.of("First param with code 'a'")) + .base(ResourceType.RESOURCE) + .type(SearchParamType.STRING) + .code(Code.of("a")) + .expression(string("extension.value as String")) + .build(); + SearchParameter sp_a2 = SearchParameter.builder() + .url(Uri.of("http://ibm.com/fhir/test/sp_a2")) + .status(PublicationStatus.ACTIVE) + .name(string("a")) + .description(Markdown.of("Second param with code 'a'")) + .base(ResourceType.RESOURCE) + .type(SearchParamType.STRING) + .code(Code.of("a")) + .expression(string("extension.value as String")) + .build(); + SearchParameter sp_b = SearchParameter.builder() + .url(Uri.of("http://ibm.com/fhir/test/sp_b")) + .status(PublicationStatus.ACTIVE) + .name(string("b")) + .base(ResourceType.RESOURCE) + .type(SearchParamType.STRING) + .description(Markdown.of("Param with code 'b'")) + .code(Code.of("b")) + .expression(string("extension.value as String")) + .build(); + SearchParameter sp_b1 = SearchParameter.builder() + .url(Uri.of("http://ibm.com/fhir/test/sp_b")) + .version("1") + .status(PublicationStatus.ACTIVE) + .name(string("b")) + .base(ResourceType.RESOURCE) + .type(SearchParamType.STRING) + .description(Markdown.of("Param with code 'b'")) + .code(Code.of("b")) + .expression(string("extension.value as String")) + .build(); + SearchParameter sp_b2 = SearchParameter.builder() + .url(Uri.of("http://ibm.com/fhir/test/sp_b")) + .version("2") + .status(PublicationStatus.ACTIVE) + .name(string("b")) + .base(ResourceType.RESOURCE) + .type(SearchParamType.STRING) + .description(Markdown.of("Param with code 'b'")) + .code(Code.of("b")) + .expression(string("extension.value as String")) + .build(); + + /** + * Add various versions of search parameters 'a' and 'b' and check the results + */ + @Test + public void testParametersMap() { + ParametersMap pm = new ParametersMap(); + pm.insert("a", sp_a1); + pm.insert("a", sp_a2); + pm.insert("b", sp_b); + pm.insert("b", sp_b1); + pm.insert("b", sp_b2); + + assertEquals(pm.lookupByCode("a"), sp_a2); + assertEquals(pm.lookupByCode("b"), sp_b2); + + assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_a1"), sp_a1); + assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_a2"), sp_a2); + assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b"), sp_b2); + assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b|1"), sp_b1); + assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b|2"), sp_b2); + + assertTrue(pm.codeEntries().size() == 2); + assertTrue(pm.canonicalEntries().size() == 5); // versionless ones for a1, a2, and b2; versioned ones for b1 and b2 + } + + /** + * Adding the same parameter under two different codes should be supported + */ + @Test + public void testParametersMap_duplicateUrl() { + ParametersMap pm = new ParametersMap(); + pm.insert("b", sp_b); + pm.insert("b2", sp_b); + + assertEquals(pm.lookupByCode("b"), sp_b); + assertEquals(pm.lookupByCode("b2"), sp_b); + + assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b"), sp_b); + + assertTrue(pm.codeEntries().size() == 2); + assertTrue(pm.canonicalEntries().size() == 1); + } + + /** + * Adding the same parameter twice should be the same as adding it once + */ + @Test + public void testParametersMap_duplicate() { + ParametersMap pm = new ParametersMap(); + pm.insert("b", sp_b); + pm.insert("b", sp_b); + + assertEquals(pm.lookupByCode("b"), sp_b); + + assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b"), sp_b); + + assertTrue(pm.codeEntries().size() == 1); + assertTrue(pm.canonicalEntries().size() == 1); + } + + /** + * Add various versions of compartment inclusion parameters 'a' and 'b' and check the results + */ + @Test + public void testParametersMap_inclusionParams() { + ParametersMap pm = new ParametersMap(); + pm.insertInclusionParam("a", sp_a1); + pm.insertInclusionParam("a", sp_a2); + pm.insertInclusionParam("b", sp_b); + pm.insertInclusionParam("b", sp_b1); + pm.insertInclusionParam("b", sp_b2); + + assertEquals(pm.getInclusionParam("a"), sp_a2); + assertEquals(pm.getInclusionParam("b"), sp_b2); + + assertTrue(pm.inclusionValues().size() == 2); + } + + /** + * Adding the same inclusion parameter twice should be the same as adding it once + */ + @Test + public void testParametersMap_inclusionParams_duplicate() { + ParametersMap pm = new ParametersMap(); + pm.insert("b", sp_b); + pm.insert("b", sp_b); + + assertEquals(pm.lookupByCode("b"), sp_b); + + assertEquals(pm.lookupByCanonical("http://ibm.com/fhir/test/sp_b"), sp_b); + + assertTrue(pm.codeEntries().size() == 1); + assertTrue(pm.canonicalEntries().size() == 1); + } } 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 a5f65c0e837..76915fb82ab 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -49,9 +49,9 @@ public void testGetSearchParameters1Default() throws Exception { @Test public void testGetSearchParameters2Default() throws Exception { // Looking for built-in and tenant-specific search parameters for "Patient" - // and "Observation". Use the default tenant since it has some Patient search + // and "Observation". Use the extended tenant since it has some Patient search // parameters defined. - FHIRRequestContext.get().setTenantId("default"); + FHIRRequestContext.get().setTenantId("extended"); Map result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); @@ -128,8 +128,8 @@ public void testGetSearchParameters5Tenant() throws Exception { @Test public void testGetSearchParameters6Default() throws Exception { - // Test filtering of search parameters for Patient (default tenant). - FHIRRequestContext.get().setTenantId("default"); + // Test filtering of search parameters for Patient (extended tenant). + FHIRRequestContext.get().setTenantId("extended"); Map result = SearchUtil.getSearchParameters("Patient"); assertNotNull(result); 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 d3c9600f2d2..2ea03489b1b 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -49,7 +49,7 @@ public void testGetAllSearchParameters() throws IOException { ParametersUtil.print(out); Assert.assertNotNull(outBA); } - assertEquals(params.size(), 1385); + assertEquals(params.size(), 1379); } @Test @@ -74,7 +74,7 @@ public void testGetTenantSPs() { Map result = ParametersUtil.getTenantSPs("default"); assertNotNull(result); assertNull(result.get("Junk")); - assertFalse(result.get("Observation").isEmpty()); + assertFalse(result.get("Observation").getCodes().isEmpty()); } @Test 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 311daebafe8..5cb7707183b 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,10 +14,11 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.FileTime; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.logging.LogManager; import org.testng.annotations.AfterMethod; @@ -27,7 +28,6 @@ import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.FHIRRequestContext; import com.ibm.fhir.model.resource.SearchParameter; -import com.ibm.fhir.model.type.code.ResourceType; /** * A base search test with utilities for other search tests @@ -53,11 +53,7 @@ public void startMethod(Method method) { // Configure the request context for our search tests FHIRRequestContext context = FHIRRequestContext.get(); - if (context == null) { - context = new FHIRRequestContext(); - } context.setOriginalRequestUri(BASE); - FHIRRequestContext.set(context); } @AfterMethod @@ -77,15 +73,17 @@ public void clearThreadLocal() { FHIRRequestContext.remove(); } - /* - * The SearchParameters return an array of values, now the printSearchParameters returns all values. - * @param label - * @param spList + /** + * Conditionally print search parameters (code and url) under a header that includes the testName + * @param testName + * @param parameters */ - @SuppressWarnings("unused") - protected void printSearchParameters(String label, Map parameters) { + protected void printSearchParameters(String testName, Map parameters) { if (DEBUG && parameters != null) { - System.out.println("\nTest: " + label + "\nSearch Parameters:"); + System.out.println("\nTest: " + testName + "\nSearch Parameters:"); + + // sort the output for convenience + SortedSet set = new TreeSet(); for (Entry entry : parameters.entrySet()) { String code = entry.getKey(); SearchParameter sp = entry.getValue(); @@ -94,11 +92,10 @@ protected void printSearchParameters(String label, Map String version = (sp.getVersion() == null) ? null : sp.getVersion().getValue(); String canonical = (version == null) ? url : url + "|" + version; - List resources = sp.getBase(); - for (ResourceType resource : resources) { - System.out.println("\t" + resource.getValue() + ":" + code + ":" + canonical); - } + set.add("\t" + code + ":\t" + canonical); } + + set.forEach(System.out::println); } } diff --git a/fhir-search/src/test/resources/config/default/extension-search-parameters.json b/fhir-search/src/test/resources/config/extended/extension-search-parameters.json similarity index 100% rename from fhir-search/src/test/resources/config/default/extension-search-parameters.json rename to fhir-search/src/test/resources/config/extended/extension-search-parameters.json diff --git a/fhir-search/src/test/resources/config/default/fhir-server-config.json b/fhir-search/src/test/resources/config/extended/fhir-server-config.json similarity index 100% rename from fhir-search/src/test/resources/config/default/fhir-server-config.json rename to fhir-search/src/test/resources/config/extended/fhir-server-config.json diff --git a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchTest.java b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchTest.java index f9d4f4fe094..162cc3a34de 100644 --- a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchTest.java +++ b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchTest.java @@ -655,12 +655,12 @@ public void testCreateObservationWithRange() throws Exception { TestUtil.assertResourceEquals(observation, responseObservation); } - @Test(groups = { "server-search" }, dependsOnMethods = {"testCreatePatient" }) + @Test(groups = { "server-search" }, dependsOnMethods = {"testCreatePatient", "testCreatePractitioner"}) public void testCreateObservation() throws Exception { WebTarget target = getWebTarget(); Observation observation = - TestUtil.buildPatientObservation(patientId, "Observation1.json"); + TestUtil.buildPatientObservation(patientId, practitionerId, "Observation1.json"); Entity entity = Entity.entity(observation, FHIRMediaType.APPLICATION_FHIR_JSON); Response response = @@ -923,6 +923,11 @@ public void testSearchPatientWithObservationRevIncluded() { + patientId, observation.getSubject().getReference().getValue()); } + /** + * 'patient' is a configured Observation search parameter for tenant1, + * so this tests that compartment info is extracted and searchable + * when the related parameter is not filtered out + */ @Test(groups = { "server-search" }, dependsOnMethods = { "testCreateObservation", "retrieveConfig" }) public void testSearchObservationWithPatientCompartment() { @@ -968,6 +973,50 @@ public void testSearchObservationWithPatientCompartmentViaPost() { assertTrue(bundle.getEntry().size() >= 1); } + /** + * 'performer' is not a configured Observation search parameter for tenant1, + * so this tests that compartment info is still extracted and searchable + * when the related parameter is filtered + */ + @Test(groups = { "server-search" }, dependsOnMethods = { + "testCreateObservation", "retrieveConfig" }) + public void testSearchObservationWithPractitionerCompartment() { + assertNotNull(compartmentSearchSupported); + if (!compartmentSearchSupported.booleanValue()) { + return; + } + + WebTarget target = getWebTarget(); + String targetUri = "Practitioner/" + practitionerId + "/Observation"; + Response response = + target.path(targetUri).request(FHIRMediaType.APPLICATION_FHIR_JSON) + .header("X-FHIR-TENANT-ID", tenantName) + .header("X-FHIR-DSID", dataStoreId) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() >= 1); + // verify link does not include consecutive '&' characters + assertTrue(bundle.getLink().size() >= 1); + assertFalse(bundle.getLink().get(0).getUrl().getValue().contains("&&")); + + // Also verify a negative search + targetUri = "Practitioner/" + practitionerId2 + "/Observation"; + response = + target.path(targetUri).request(FHIRMediaType.APPLICATION_FHIR_JSON) + .header("X-FHIR-TENANT-ID", tenantName) + .header("X-FHIR-DSID", dataStoreId) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + bundle = response.readEntity(Bundle.class); + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() == 0); + // verify link does not include consecutive '&' characters + assertTrue(bundle.getLink().size() >= 1); + assertFalse(bundle.getLink().get(0).getUrl().getValue().contains("&&")); + } + @Test(groups = { "server-search" }, dependsOnMethods = {"testCreateObservation" }) public void test_SearchObservationWithSubject() throws Exception { FHIRParameters parameters = new FHIRParameters();