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