001/*
002 * #%L
003 * HAPI FHIR JPA - Search Parameters
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.jpa.searchparam.extractor;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.jpa.model.config.PartitionSettings;
024import ca.uhn.fhir.jpa.model.entity.StorageSettings;
025import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
026import ca.uhn.fhir.sl.cache.Cache;
027import ca.uhn.fhir.sl.cache.CacheFactory;
028import ca.uhn.fhir.util.BundleUtil;
029import com.google.common.annotations.VisibleForTesting;
030import jakarta.annotation.PostConstruct;
031import org.hl7.fhir.exceptions.FHIRException;
032import org.hl7.fhir.exceptions.PathEngineException;
033import org.hl7.fhir.instance.model.api.IBase;
034import org.hl7.fhir.r4.context.IWorkerContext;
035import org.hl7.fhir.r4.fhirpath.ExpressionNode;
036import org.hl7.fhir.r4.fhirpath.FHIRPathEngine;
037import org.hl7.fhir.r4.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
038import org.hl7.fhir.r4.fhirpath.TypeDetails;
039import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
040import org.hl7.fhir.r4.model.Base;
041import org.hl7.fhir.r4.model.IdType;
042import org.hl7.fhir.r4.model.Resource;
043import org.hl7.fhir.r4.model.ResourceType;
044import org.hl7.fhir.r4.model.ValueSet;
045
046import java.util.Collections;
047import java.util.HashMap;
048import java.util.List;
049import java.util.Map;
050import java.util.concurrent.TimeUnit;
051
052import static org.apache.commons.lang3.StringUtils.isNotBlank;
053
054public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements ISearchParamExtractor {
055
056        private Cache<String, ExpressionNode> myParsedFhirPathCache;
057        private FHIRPathEngine myFhirPathEngine;
058
059        /**
060         * Constructor
061         */
062        public SearchParamExtractorR4() {
063                super();
064        }
065
066        // This constructor is used by tests
067        @VisibleForTesting
068        public SearchParamExtractorR4(
069                        StorageSettings theStorageSettings,
070                        PartitionSettings thePartitionSettings,
071                        FhirContext theCtx,
072                        ISearchParamRegistry theSearchParamRegistry) {
073                super(theStorageSettings, thePartitionSettings, theCtx, theSearchParamRegistry);
074                initFhirPath();
075                start();
076        }
077
078        @Override
079        public IValueExtractor getPathValueExtractor(IBase theResource, String theSinglePath) {
080                return () -> {
081                        ExpressionNode parsed = myParsedFhirPathCache.get(theSinglePath, path -> myFhirPathEngine.parse(path));
082                        return myFhirPathEngine.evaluate(
083                                        theResource, (Base) theResource, (Base) theResource, (Base) theResource, parsed);
084                };
085        }
086
087        @Override
088        @PostConstruct
089        public void start() {
090                super.start();
091                if (myFhirPathEngine == null) {
092                        initFhirPath();
093                }
094        }
095
096        public void initFhirPath() {
097                IWorkerContext worker = new HapiWorkerContext(getContext(), getContext().getValidationSupport());
098                myFhirPathEngine = new FHIRPathEngine(worker);
099                myFhirPathEngine.setHostServices(new SearchParamExtractorR4HostServices());
100
101                myParsedFhirPathCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(10));
102        }
103
104        private class SearchParamExtractorR4HostServices implements FHIRPathEngine.IEvaluationContext {
105
106                private final Map<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
107
108                @Override
109                public List<Base> resolveConstant(
110                                FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant)
111                                throws PathEngineException {
112                        return Collections.emptyList();
113                }
114
115                @Override
116                public TypeDetails resolveConstantType(
117                                FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant)
118                                throws PathEngineException {
119                        return null;
120                }
121
122                @Override
123                public boolean log(String argument, List<Base> focus) {
124                        return false;
125                }
126
127                @Override
128                public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) {
129                        return null;
130                }
131
132                @Override
133                public TypeDetails checkFunction(
134                                FHIRPathEngine engine,
135                                Object appContext,
136                                String functionName,
137                                TypeDetails focus,
138                                List<TypeDetails> parameters)
139                                throws PathEngineException {
140                        return null;
141                }
142
143                @Override
144                public List<Base> executeFunction(
145                                FHIRPathEngine engine,
146                                Object appContext,
147                                List<Base> focus,
148                                String functionName,
149                                List<List<Base>> parameters) {
150                        return null;
151                }
152
153                @Override
154                public Base resolveReference(FHIRPathEngine engine, Object theAppContext, String theUrl, Base refContext) {
155                        Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, theAppContext);
156                        if (retVal != null) {
157                                return retVal;
158                        }
159
160                        /*
161                         * When we're doing resolution within the SearchParamExtractor, if we want
162                         * to do a resolve() it's just to check the type, so there is no point
163                         * going through the heavyweight test. We can just return a stub and
164                         * that's good enough since we're just doing something like
165                         *    Encounter.patient.where(resolve() is Patient)
166                         */
167                        IdType url = new IdType(theUrl);
168                        if (isNotBlank(url.getResourceType())) {
169
170                                retVal = myResourceTypeToStub.get(url.getResourceType());
171                                if (retVal != null) {
172                                        return retVal;
173                                }
174
175                                ResourceType resourceType = ResourceType.fromCode(url.getResourceType());
176                                if (resourceType != null) {
177                                        retVal = new Resource() {
178                                                private static final long serialVersionUID = -5303169871827706447L;
179
180                                                @Override
181                                                public Resource copy() {
182                                                        return this;
183                                                }
184
185                                                @Override
186                                                public ResourceType getResourceType() {
187                                                        return resourceType;
188                                                }
189
190                                                @Override
191                                                public String fhirType() {
192                                                        return url.getResourceType();
193                                                }
194                                        };
195                                        myResourceTypeToStub.put(url.getResourceType(), retVal);
196                                }
197                        }
198                        return retVal;
199                }
200
201                @Override
202                public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url)
203                                throws FHIRException {
204                        return false;
205                }
206
207                @Override
208                public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) {
209                        return null;
210                }
211        }
212}