001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2023 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 org.apache.commons.lang3.Validate; 028import org.hl7.fhir.instance.model.api.IBase; 029import org.hl7.fhir.instance.model.api.IBaseResource; 030import org.hl7.fhir.instance.model.api.IPrimitiveType; 031 032import javax.annotation.Nullable; 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(FhirContext theFhirContext, String theResourceType) { 066 RuntimeSearchParam myPatientSearchParam = null; 067 RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 068 myPatientSearchParam = runtimeResourceDefinition.getSearchParam("patient"); 069 if (myPatientSearchParam == null) { 070 myPatientSearchParam = runtimeResourceDefinition.getSearchParam("subject"); 071 if (myPatientSearchParam == null) { 072 myPatientSearchParam = getOnlyPatientCompartmentRuntimeSearchParam(runtimeResourceDefinition); 073 } 074 } 075 return Optional.ofNullable(myPatientSearchParam); 076 } 077 078 /** 079 * Given the resource type, fetch all its patient-based search parameter name that's available 080 */ 081 public static Set<String> getPatientSearchParamsForResourceType(FhirContext theFhirContext, String theResourceType) { 082 RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 083 084 List<RuntimeSearchParam> searchParams = new ArrayList<>(runtimeResourceDefinition.getSearchParamsForCompartmentName("Patient")); 085 // add patient search parameter for resources that's not in the compartment 086 RuntimeSearchParam myPatientSearchParam = runtimeResourceDefinition.getSearchParam("patient"); 087 if (myPatientSearchParam != null) { 088 searchParams.add(myPatientSearchParam); 089 } 090 RuntimeSearchParam mySubjectSearchParam = runtimeResourceDefinition.getSearchParam("subject"); 091 if (mySubjectSearchParam != null) { 092 searchParams.add(mySubjectSearchParam); 093 } 094 if (searchParams == null || searchParams.size() == 0) { 095 String errorMessage = String.format("Resource type [%s] is not eligible for this type of export, as it contains no Patient compartment, and no `patient` or `subject` search parameter", runtimeResourceDefinition.getId()); 096 throw new IllegalArgumentException(Msg.code(2222) + errorMessage); 097 } 098 // deduplicate list of searchParams and get their names 099 return searchParams.stream().map(RuntimeSearchParam::getName).collect(Collectors.toSet()); 100 } 101 102 /** 103 * Search the resource definition for a compartment named 'patient' and return its related Search Parameter. 104 */ 105 public static RuntimeSearchParam getOnlyPatientCompartmentRuntimeSearchParam(FhirContext theFhirContext, String theResourceType) { 106 RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 107 return getOnlyPatientCompartmentRuntimeSearchParam(resourceDefinition); 108 } 109 110 public static RuntimeSearchParam getOnlyPatientCompartmentRuntimeSearchParam(RuntimeResourceDefinition runtimeResourceDefinition) { 111 RuntimeSearchParam patientSearchParam; 112 List<RuntimeSearchParam> searchParams = runtimeResourceDefinition.getSearchParamsForCompartmentName("Patient"); 113 if (searchParams == null || searchParams.size() == 0) { 114 String errorMessage = String.format("Resource type [%s] is not eligible for this type of export, as it contains no Patient compartment, and no `patient` or `subject` search parameter", runtimeResourceDefinition.getId()); 115 throw new IllegalArgumentException(Msg.code(1774) + errorMessage); 116 } else if (searchParams.size() == 1) { 117 patientSearchParam = searchParams.get(0); 118 } else { 119 String errorMessage = String.format("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.", runtimeResourceDefinition.getId()); 120 throw new IllegalArgumentException(Msg.code(1775) + errorMessage); 121 } 122 return patientSearchParam; 123 } 124 125 public static List<RuntimeSearchParam> getAllPatientCompartmentRuntimeSearchParamsForResourceType(FhirContext theFhirContext, String theResourceType) { 126 RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 127 return getAllPatientCompartmentRuntimeSearchParams(runtimeResourceDefinition); 128 } 129 130 public static List<RuntimeSearchParam> getAllPatientCompartmenRuntimeSearchParams(FhirContext theFhirContext) { 131 return theFhirContext.getResourceTypes() 132 .stream() 133 .flatMap(type -> getAllPatientCompartmentRuntimeSearchParamsForResourceType(theFhirContext, type).stream()) 134 .collect(Collectors.toList()); 135 } 136 137 public static Set<String> getAllResourceTypesThatAreInPatientCompartment(FhirContext theFhirContext) { 138 return theFhirContext.getResourceTypes().stream() 139 .filter(type -> getAllPatientCompartmentRuntimeSearchParamsForResourceType(theFhirContext, type).size() > 0) 140 .collect(Collectors.toSet()); 141 142 } 143 144 private static List<RuntimeSearchParam> getAllPatientCompartmentRuntimeSearchParams(RuntimeResourceDefinition theRuntimeResourceDefinition) { 145 List<RuntimeSearchParam> patient = theRuntimeResourceDefinition.getSearchParamsForCompartmentName("Patient"); 146 return patient; 147 } 148 149 150 /** 151 * Return true if any search parameter in the resource can point at a patient, false otherwise 152 */ 153 public static boolean isResourceTypeInPatientCompartment(FhirContext theFhirContext, String theResourceType) { 154 RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType); 155 return getAllPatientCompartmentRuntimeSearchParams(runtimeResourceDefinition).size() > 0; 156 } 157 158 159 @Nullable 160 public static String getCode(FhirContext theContext, IBaseResource theResource) { 161 return getStringChild(theContext, theResource, "code"); 162 } 163 164 @Nullable 165 public static String getURL(FhirContext theContext, IBaseResource theResource) { 166 return getStringChild(theContext, theResource, "url"); 167 } 168 169 @Nullable 170 public static String getExpression(FhirContext theFhirContext, IBaseResource theResource) { 171 return getStringChild(theFhirContext, theResource, "expression"); 172 } 173 174 private static String getStringChild(FhirContext theFhirContext, IBaseResource theResource, String theChildName) { 175 Validate.notNull(theFhirContext, "theContext must not be null"); 176 Validate.notNull(theResource, "theResource must not be null"); 177 RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResource); 178 179 BaseRuntimeChildDefinition base = def.getChildByName(theChildName); 180 return base 181 .getAccessor() 182 .getFirstValueOrNull(theResource) 183 .map(t -> ((IPrimitiveType<?>) t)) 184 .map(t -> t.getValueAsString()) 185 .orElse(null); 186 } 187 188 public static String stripModifier(String theSearchParam) { 189 String retval; 190 int colonIndex = theSearchParam.indexOf(":"); 191 if (colonIndex == -1) { 192 retval = theSearchParam; 193 } else { 194 retval = theSearchParam.substring(0, colonIndex); 195 } 196 return retval; 197 } 198}