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}