
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}