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