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}