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.fhirpath.ExpressionNode; 035import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 036import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails; 037import org.hl7.fhir.r5.fhirpath.TypeDetails; 038import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; 039import org.hl7.fhir.r5.model.Base; 040import org.hl7.fhir.r5.model.IdType; 041import org.hl7.fhir.r5.model.Resource; 042import org.hl7.fhir.r5.model.ResourceType; 043import org.hl7.fhir.r5.model.ValueSet; 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( 107 FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) 108 throws PathEngineException { 109 return Collections.emptyList(); 110 } 111 112 @Override 113 public TypeDetails resolveConstantType( 114 FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) 115 throws PathEngineException { 116 return null; 117 } 118 119 @Override 120 public boolean log(String argument, List<Base> focus) { 121 return false; 122 } 123 124 @Override 125 public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) { 126 return null; 127 } 128 129 @Override 130 public TypeDetails checkFunction( 131 FHIRPathEngine engine, 132 Object appContext, 133 String functionName, 134 TypeDetails focus, 135 List<TypeDetails> parameters) 136 throws PathEngineException { 137 return null; 138 } 139 140 @Override 141 public List<Base> executeFunction( 142 FHIRPathEngine engine, 143 Object appContext, 144 List<Base> focus, 145 String functionName, 146 List<List<Base>> parameters) { 147 return null; 148 } 149 150 @Override 151 public Base resolveReference(FHIRPathEngine engine, Object appContext, String theUrl, Base refContext) { 152 Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, appContext); 153 if (retVal != null) { 154 return retVal; 155 } 156 157 /* 158 * When we're doing resolution within the SearchParamExtractor, if we want 159 * to do a resolve() it's just to check the type, so there is no point 160 * going through the heavyweight test. We can just return a stub and 161 * that's good enough since we're just doing something like 162 * Encounter.patient.where(resolve() is Patient) 163 */ 164 IdType url = new IdType(theUrl); 165 if (isNotBlank(url.getResourceType())) { 166 167 retVal = myResourceTypeToStub.get(url.getResourceType()); 168 if (retVal != null) { 169 return retVal; 170 } 171 172 ResourceType resourceType = ResourceType.fromCode(url.getResourceType()); 173 if (resourceType != null) { 174 retVal = new Resource() { 175 private static final long serialVersionUID = 2368522971330181178L; 176 177 @Override 178 public Resource copy() { 179 return this; 180 } 181 182 @Override 183 public ResourceType getResourceType() { 184 return resourceType; 185 } 186 187 @Override 188 public String fhirType() { 189 return url.getResourceType(); 190 } 191 }; 192 myResourceTypeToStub.put(url.getResourceType(), retVal); 193 } 194 } 195 return retVal; 196 } 197 198 @Override 199 public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) 200 throws FHIRException { 201 return false; 202 } 203 204 @Override 205 public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { 206 return null; 207 } 208 209 @Override 210 public boolean paramIsType(String name, int index) { 211 return false; 212 } 213 } 214}