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