001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.util; 021 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.context.RuntimeResourceDefinition; 025import ca.uhn.fhir.context.RuntimeSearchParam; 026import ca.uhn.fhir.i18n.Msg; 027import jakarta.annotation.Nullable; 028import org.apache.commons.lang3.Validate; 029import org.hl7.fhir.instance.model.api.IBase; 030import org.hl7.fhir.instance.model.api.IBaseResource; 031import org.hl7.fhir.instance.model.api.IPrimitiveType; 032 033import java.util.ArrayList; 034import java.util.List; 035import java.util.Optional; 036import java.util.Set; 037import java.util.stream.Collectors; 038 039public class SearchParameterUtil { 040 041 public static List<String> getBaseAsStrings(FhirContext theContext, IBaseResource theResource) { 042 Validate.notNull(theContext, "theContext must not be null"); 043 Validate.notNull(theResource, "theResource must not be null"); 044 RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); 045 046 BaseRuntimeChildDefinition base = def.getChildByName("base"); 047 List<IBase> baseValues = base.getAccessor().getValues(theResource); 048 List<String> retVal = new ArrayList<>(); 049 for (IBase next : baseValues) { 050 IPrimitiveType<?> nextPrimitive = (IPrimitiveType<?>) next; 051 retVal.add(nextPrimitive.getValueAsString()); 052 } 053 054 return retVal; 055 } 056 057 /** 058 * Given the resource type, fetch its patient-based search parameter name 059 * 1. Attempt to find one called 'patient' 060 * 2. If that fails, find one called 'subject' 061 * 3. If that fails, find one by Patient Compartment. 062 * 3.1 If that returns >1 result, throw an error 063 * 3.2 If that returns 1 result, return it 064 */ 065 public static Optional<RuntimeSearchParam> getOnlyPatientSearchParamForResourceType( 066 FhirContext theFhirContext, String theResourceType) { 067 RuntimeSearchParam myPatientSearchParam = null; 068 RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 069 myPatientSearchParam = runtimeResourceDefinition.getSearchParam("patient"); 070 if (myPatientSearchParam == null) { 071 myPatientSearchParam = runtimeResourceDefinition.getSearchParam("subject"); 072 if (myPatientSearchParam == null) { 073 myPatientSearchParam = getOnlyPatientCompartmentRuntimeSearchParam(runtimeResourceDefinition); 074 } 075 } 076 return Optional.ofNullable(myPatientSearchParam); 077 } 078 079 /** 080 * Given the resource type, fetch all its patient-based search parameter name that's available 081 */ 082 public static Set<String> getPatientSearchParamsForResourceType( 083 FhirContext theFhirContext, String theResourceType) { 084 RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 085 086 List<RuntimeSearchParam> searchParams = 087 new ArrayList<>(runtimeResourceDefinition.getSearchParamsForCompartmentName("Patient")); 088 // add patient search parameter for resources that's not in the compartment 089 RuntimeSearchParam myPatientSearchParam = runtimeResourceDefinition.getSearchParam("patient"); 090 if (myPatientSearchParam != null) { 091 searchParams.add(myPatientSearchParam); 092 } 093 RuntimeSearchParam mySubjectSearchParam = runtimeResourceDefinition.getSearchParam("subject"); 094 if (mySubjectSearchParam != null) { 095 searchParams.add(mySubjectSearchParam); 096 } 097 if (searchParams == null || searchParams.size() == 0) { 098 String errorMessage = String.format( 099 "Resource type [%s] is not eligible for this type of export, as it contains no Patient compartment, and no `patient` or `subject` search parameter", 100 runtimeResourceDefinition.getId()); 101 throw new IllegalArgumentException(Msg.code(2222) + errorMessage); 102 } 103 // deduplicate list of searchParams and get their names 104 return searchParams.stream().map(RuntimeSearchParam::getName).collect(Collectors.toSet()); 105 } 106 107 /** 108 * Search the resource definition for a compartment named 'patient' and return its related Search Parameter. 109 */ 110 public static RuntimeSearchParam getOnlyPatientCompartmentRuntimeSearchParam( 111 FhirContext theFhirContext, String theResourceType) { 112 RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 113 return getOnlyPatientCompartmentRuntimeSearchParam(resourceDefinition); 114 } 115 116 public static RuntimeSearchParam getOnlyPatientCompartmentRuntimeSearchParam( 117 RuntimeResourceDefinition runtimeResourceDefinition) { 118 RuntimeSearchParam patientSearchParam; 119 List<RuntimeSearchParam> searchParams = runtimeResourceDefinition.getSearchParamsForCompartmentName("Patient"); 120 if (searchParams == null || searchParams.size() == 0) { 121 String errorMessage = String.format( 122 "Resource type [%s] is not eligible for this type of export, as it contains no Patient compartment, and no `patient` or `subject` search parameter", 123 runtimeResourceDefinition.getId()); 124 throw new IllegalArgumentException(Msg.code(1774) + errorMessage); 125 } else if (searchParams.size() == 1) { 126 patientSearchParam = searchParams.get(0); 127 } else { 128 String errorMessage = String.format( 129 "Resource type %s has more than one Search Param which references a patient compartment. We are unable to disambiguate which patient search parameter we should be searching by.", 130 runtimeResourceDefinition.getId()); 131 throw new IllegalArgumentException(Msg.code(1775) + errorMessage); 132 } 133 return patientSearchParam; 134 } 135 136 public static List<RuntimeSearchParam> getAllPatientCompartmentRuntimeSearchParamsForResourceType( 137 FhirContext theFhirContext, String theResourceType) { 138 RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 139 return getAllPatientCompartmentRuntimeSearchParams(runtimeResourceDefinition); 140 } 141 142 public static List<RuntimeSearchParam> getAllPatientCompartmenRuntimeSearchParams(FhirContext theFhirContext) { 143 return theFhirContext.getResourceTypes().stream() 144 .flatMap(type -> 145 getAllPatientCompartmentRuntimeSearchParamsForResourceType(theFhirContext, type).stream()) 146 .collect(Collectors.toList()); 147 } 148 149 public static Set<String> getAllResourceTypesThatAreInPatientCompartment(FhirContext theFhirContext) { 150 return theFhirContext.getResourceTypes().stream() 151 .filter(type -> getAllPatientCompartmentRuntimeSearchParamsForResourceType(theFhirContext, type) 152 .size() 153 > 0) 154 .collect(Collectors.toSet()); 155 } 156 157 private static List<RuntimeSearchParam> getAllPatientCompartmentRuntimeSearchParams( 158 RuntimeResourceDefinition theRuntimeResourceDefinition) { 159 List<RuntimeSearchParam> patient = theRuntimeResourceDefinition.getSearchParamsForCompartmentName("Patient"); 160 return patient; 161 } 162 163 /** 164 * Return true if any search parameter in the resource can point at a patient, false otherwise 165 */ 166 public static boolean isResourceTypeInPatientCompartment(FhirContext theFhirContext, String theResourceType) { 167 RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 168 return getAllPatientCompartmentRuntimeSearchParams(runtimeResourceDefinition) 169 .size() 170 > 0; 171 } 172 173 @Nullable 174 public static String getCode(FhirContext theContext, IBaseResource theResource) { 175 return getStringChild(theContext, theResource, "code"); 176 } 177 178 @Nullable 179 public static String getURL(FhirContext theContext, IBaseResource theResource) { 180 return getStringChild(theContext, theResource, "url"); 181 } 182 183 @Nullable 184 public static String getExpression(FhirContext theFhirContext, IBaseResource theResource) { 185 return getStringChild(theFhirContext, theResource, "expression"); 186 } 187 188 private static String getStringChild(FhirContext theFhirContext, IBaseResource theResource, String theChildName) { 189 Validate.notNull(theFhirContext, "theContext must not be null"); 190 Validate.notNull(theResource, "theResource must not be null"); 191 RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResource); 192 193 BaseRuntimeChildDefinition base = def.getChildByName(theChildName); 194 return base.getAccessor() 195 .getFirstValueOrNull(theResource) 196 .map(t -> ((IPrimitiveType<?>) t)) 197 .map(t -> t.getValueAsString()) 198 .orElse(null); 199 } 200 201 public static String stripModifier(String theSearchParam) { 202 String retval; 203 int colonIndex = theSearchParam.indexOf(":"); 204 if (colonIndex == -1) { 205 retval = theSearchParam; 206 } else { 207 retval = theSearchParam.substring(0, colonIndex); 208 } 209 return retval; 210 } 211}