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}