View Javadoc
1   package ca.uhn.fhir.jpa.dao.r4;
2   
3   /*
4    * #%L
5    * HAPI FHIR JPA Server
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   * http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.context.ConfigurationException;
24  import ca.uhn.fhir.context.FhirContext;
25  import ca.uhn.fhir.context.RuntimeSearchParam;
26  import ca.uhn.fhir.jpa.dao.*;
27  import ca.uhn.fhir.jpa.entity.*;
28  import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
29  import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
30  import com.google.common.annotations.VisibleForTesting;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.lang3.tuple.Pair;
33  import org.hl7.fhir.exceptions.FHIRException;
34  import org.hl7.fhir.instance.model.api.IBase;
35  import org.hl7.fhir.instance.model.api.IBaseResource;
36  import org.hl7.fhir.instance.model.api.IPrimitiveType;
37  import org.hl7.fhir.r4.context.IWorkerContext;
38  import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
39  import org.hl7.fhir.r4.model.*;
40  import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestSecurityComponent;
41  import org.hl7.fhir.r4.model.Enumeration;
42  import org.hl7.fhir.r4.model.Location.LocationPositionComponent;
43  import org.hl7.fhir.r4.model.Patient.PatientCommunicationComponent;
44  import org.hl7.fhir.r4.utils.FHIRPathEngine;
45  import org.springframework.beans.factory.annotation.Autowired;
46  
47  import javax.measure.unit.NonSI;
48  import javax.measure.unit.Unit;
49  import java.math.BigDecimal;
50  import java.util.*;
51  
52  import static org.apache.commons.lang3.StringUtils.isBlank;
53  import static org.apache.commons.lang3.StringUtils.isNotBlank;
54  
55  public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements ISearchParamExtractor {
56  
57  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorR4.class);
58  
59  	@Autowired
60  	private org.hl7.fhir.r4.hapi.ctx.IValidationSupport myValidationSupport;
61  
62  	/**
63  	 * Constructor
64  	 */
65  	public SearchParamExtractorR4() {
66  		super();
67  	}
68  
69  	public SearchParamExtractorR4(DaoConfig theDaoConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
70  		super(theDaoConfig, theCtx, theSearchParamRegistry);
71  		myValidationSupport = theValidationSupport;
72  	}
73  
74  	private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
75  		if (!nextValue.getValueElement().isEmpty()) {
76  			BigDecimal nextValueValue = nextValue.getValueElement().getValue();
77  			String nextValueString = nextValue.getSystemElement().getValueAsString();
78  			String nextValueCode = nextValue.getCode();
79  			ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
80  			nextEntity.setResource(theEntity);
81  			retVal.add(nextEntity);
82  		}
83  	}
84  
85  	private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
86  		if (isBlank(searchTerm)) {
87  			return;
88  		}
89  		if (searchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
90  			searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
91  		}
92  
93  		ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), resourceName, BaseHapiFhirDao.normalizeString(searchTerm), searchTerm);
94  		nextEntity.setResource(theEntity);
95  		retVal.add(nextEntity);
96  	}
97  
98  	private void addStringParam(ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> retVal, RuntimeSearchParam nextSpDef, String value) {
99  		if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
100 			value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
101 		}
102 		ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), nextSpDef.getName(), BaseHapiFhirDao.normalizeString(value), value);
103 		nextEntity.setResource(theEntity);
104 		retVal.add(nextEntity);
105 	}
106 
107 	@Override
108 	public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
109 		ArrayList<PathAndRef> retVal = new ArrayList<>();
110 
111 		String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
112 		for (String path : nextPathsSplit) {
113 			path = path.trim();
114 			if (isNotBlank(path)) {
115 				for (Object next : extractValues(path, theResource)) {
116 					retVal.add(new PathAndRef(path, next));
117 				}
118 			}
119 		}
120 
121 		return retVal;
122 	}
123 
124 	@Override
125 	public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
126 		// TODO: implement
127 		return Collections.emptySet();
128 	}
129 
130 	/*
131 	 * (non-Javadoc)
132 	 *
133 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamDates(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
134 	 */
135 	@Override
136 	public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
137 		HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
138 
139 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
140 		for (RuntimeSearchParam nextSpDef : searchParams) {
141 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
142 				continue;
143 			}
144 
145 			String nextPath = nextSpDef.getPath();
146 			if (isBlank(nextPath)) {
147 				continue;
148 			}
149 
150 			boolean multiType = false;
151 			if (nextPath.endsWith("[x]")) {
152 				multiType = true;
153 			}
154 
155 			for (Object nextObject : extractValues(nextPath, theResource)) {
156 				if (nextObject == null) {
157 					continue;
158 				}
159 
160 				ResourceIndexedSearchParamDate nextEntity;
161 				if (nextObject instanceof BaseDateTimeType) {
162 					BaseDateTimeType nextValue = (BaseDateTimeType) nextObject;
163 					if (nextValue.isEmpty()) {
164 						continue;
165 					}
166 					nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
167 				} else if (nextObject instanceof Period) {
168 					Period nextValue = (Period) nextObject;
169 					if (nextValue.isEmpty()) {
170 						continue;
171 					}
172 					nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
173 				} else if (nextObject instanceof Timing) {
174 					Timing nextValue = (Timing) nextObject;
175 					if (nextValue.isEmpty()) {
176 						continue;
177 					}
178 					TreeSet<Date> dates = new TreeSet<>();
179 					String firstValue = null;
180 					for (DateTimeType nextEvent : nextValue.getEvent()) {
181 						if (nextEvent.getValue() != null) {
182 							dates.add(nextEvent.getValue());
183 							if (firstValue == null) {
184 								firstValue = nextEvent.getValueAsString();
185 							}
186 						}
187 					}
188 					if (dates.isEmpty()) {
189 						continue;
190 					}
191 
192 					nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), dates.first(), dates.last(), firstValue);
193 				} else if (nextObject instanceof StringType) {
194 					// CarePlan.activitydate can be a string
195 					continue;
196 				} else {
197 					if (!multiType) {
198 						throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
199 					} else {
200 						continue;
201 					}
202 				}
203 				if (nextEntity != null) {
204 					nextEntity.setResource(theEntity);
205 					retVal.add(nextEntity);
206 				}
207 			}
208 		}
209 
210 		return retVal;
211 	}
212 
213 	/*
214 	 * (non-Javadoc)
215 	 *
216 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamNumber(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
217 	 */
218 	@Override
219 	public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
220 		HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<>();
221 
222 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
223 		for (RuntimeSearchParam nextSpDef : searchParams) {
224 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
225 				continue;
226 			}
227 
228 			String nextPath = nextSpDef.getPath();
229 			if (isBlank(nextPath)) {
230 				continue;
231 			}
232 
233 			for (Object nextObject : extractValues(nextPath, theResource)) {
234 				if (nextObject == null || ((IBase) nextObject).isEmpty()) {
235 					continue;
236 				}
237 
238 				String resourceName = nextSpDef.getName();
239 				boolean multiType = false;
240 				if (nextPath.endsWith("[x]")) {
241 					multiType = true;
242 				}
243 
244 				if (nextObject instanceof Duration) {
245 					Duration nextValue = (Duration) nextObject;
246 					if (nextValue.getValueElement().isEmpty()) {
247 						continue;
248 					}
249 
250 					if (BaseHapiFhirDao.UCUM_NS.equals(nextValue.getSystem())) {
251 						if (isNotBlank(nextValue.getCode())) {
252 
253 							Unit<? extends javax.measure.quantity.Quantity> unit = Unit.valueOf(nextValue.getCode());
254 							javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
255 							double dayValue = dayConverter.convert(nextValue.getValue().doubleValue());
256 							Duration newValue = new Duration();
257 							newValue.setSystem(BaseHapiFhirDao.UCUM_NS);
258 							newValue.setCode(NonSI.DAY.toString());
259 							newValue.setValue(dayValue);
260 							nextValue = newValue;
261 
262 							/*
263 							 * @SuppressWarnings("unchecked") PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>> unit = (PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>>)
264 							 * UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null); if (unit.isCompatible(UCUM.DAY)) {
265 							 *
266 							 * @SuppressWarnings("unchecked") PhysicsUnit<org.unitsofmeasurement.quantity.Time> timeUnit = (PhysicsUnit<Time>) unit; UnitConverter conv = timeUnit.getConverterTo(UCUM.DAY);
267 							 * double dayValue = conv.convert(nextValue.getValue().getValue().doubleValue()); Duration newValue = new Duration(); newValue.setSystem(UCUM_NS);
268 							 * newValue.setCode(UCUM.DAY.getSymbol()); newValue.setValue(dayValue); nextValue=newValue; }
269 							 */
270 						}
271 					}
272 
273 					ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
274 					nextEntity.setResource(theEntity);
275 					retVal.add(nextEntity);
276 				} else if (nextObject instanceof Quantity) {
277 					Quantity nextValue = (Quantity) nextObject;
278 					if (nextValue.getValueElement().isEmpty()) {
279 						continue;
280 					}
281 
282 					ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
283 					nextEntity.setResource(theEntity);
284 					retVal.add(nextEntity);
285 				} else if (nextObject instanceof IntegerType) {
286 					IntegerType nextValue = (IntegerType) nextObject;
287 					if (nextValue.getValue() == null) {
288 						continue;
289 					}
290 
291 					ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, new BigDecimal(nextValue.getValue()));
292 					nextEntity.setResource(theEntity);
293 					retVal.add(nextEntity);
294 				} else if (nextObject instanceof DecimalType) {
295 					DecimalType nextValue = (DecimalType) nextObject;
296 					if (nextValue.getValue() == null) {
297 						continue;
298 					}
299 
300 					ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
301 					nextEntity.setResource(theEntity);
302 					retVal.add(nextEntity);
303 				} else {
304 					if (!multiType) {
305 						throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
306 					} else {
307 						continue;
308 					}
309 				}
310 			}
311 		}
312 
313 		return retVal;
314 	}
315 
316 	/*
317 	 * (non-Javadoc)
318 	 *
319 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamQuantity(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
320 	 */
321 	@Override
322 	public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
323 		HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<>();
324 
325 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
326 		for (RuntimeSearchParam nextSpDef : searchParams) {
327 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
328 				continue;
329 			}
330 
331 			String nextPath = nextSpDef.getPath();
332 			if (isBlank(nextPath)) {
333 				continue;
334 			}
335 
336 			for (Object nextObject : extractValues(nextPath, theResource)) {
337 				if (nextObject == null || ((IBase) nextObject).isEmpty()) {
338 					continue;
339 				}
340 
341 				String resourceName = nextSpDef.getName();
342 				boolean multiType = false;
343 				if (nextPath.endsWith("[x]")) {
344 					multiType = true;
345 				}
346 
347 				if (nextObject instanceof Quantity) {
348 					Quantity nextValue = (Quantity) nextObject;
349 					addQuantity(theEntity, retVal, resourceName, nextValue);
350 				} else if (nextObject instanceof Range) {
351 					Range nextValue = (Range) nextObject;
352 					addQuantity(theEntity, retVal, resourceName, nextValue.getLow());
353 					addQuantity(theEntity, retVal, resourceName, nextValue.getHigh());
354 				} else if (nextObject instanceof LocationPositionComponent) {
355 					continue;
356 				} else {
357 					if (!multiType) {
358 						throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
359 					} else {
360 						continue;
361 					}
362 				}
363 			}
364 		}
365 
366 		return retVal;
367 	}
368 
369 	/*
370 	 * (non-Javadoc)
371 	 *
372 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamStrings(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
373 	 */
374 	@Override
375 	public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
376 		HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<>();
377 
378 		String resourceName = getContext().getResourceDefinition(theResource).getName();
379 
380 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
381 		for (RuntimeSearchParam nextSpDef : searchParams) {
382 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
383 				continue;
384 			}
385 
386 			String nextPath = nextSpDef.getPath();
387 			String nextSpName = nextSpDef.getName();
388 
389 			if (isBlank(nextPath)) {
390 
391 				// // TODO: implement phonetic, and any others that have no path
392 				//
393 				// // TODO: do we still need this check?
394 				// if ("Questionnaire".equals(nextSpName) && nextSpDef.getName().equals("title")) {
395 				// Questionnaire q = (Questionnaire) theResource;
396 				// String title = "";// q.getGroup().getTitle();
397 				// addSearchTerm(theEntity, retVal, nextSpName, title);
398 				// }
399 
400 				continue;
401 			}
402 
403 			for (Object nextObject : extractValues(nextPath, theResource)) {
404 				if (nextObject == null || ((IBase) nextObject).isEmpty()) {
405 					continue;
406 				}
407 
408 				boolean multiType = false;
409 				if (nextPath.endsWith("[x]")) {
410 					multiType = true;
411 				}
412 
413 				if (nextObject instanceof IPrimitiveType<?>) {
414 					IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
415 					String searchTerm = nextValue.getValueAsString();
416 					addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
417 				} else {
418 					if (nextObject instanceof HumanName) {
419 						ArrayList<StringType> allNames = new ArrayList<>();
420 						HumanName nextHumanName = (HumanName) nextObject;
421 						if (isNotBlank(nextHumanName.getFamily())) {
422 							allNames.add(nextHumanName.getFamilyElement());
423 						}
424 						allNames.addAll(nextHumanName.getGiven());
425 						for (StringType nextName : allNames) {
426 							addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
427 						}
428 					} else if (nextObject instanceof Address) {
429 						ArrayList<StringType> allNames = new ArrayList<>();
430 						Address nextAddress = (Address) nextObject;
431 						allNames.addAll(nextAddress.getLine());
432 						allNames.add(nextAddress.getCityElement());
433 						allNames.add(nextAddress.getStateElement());
434 						allNames.add(nextAddress.getCountryElement());
435 						allNames.add(nextAddress.getPostalCodeElement());
436 						for (StringType nextName : allNames) {
437 							addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
438 						}
439 					} else if (nextObject instanceof ContactPoint) {
440 						ContactPoint nextContact = (ContactPoint) nextObject;
441 						if (nextContact.getValueElement().isEmpty() == false) {
442 							addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue());
443 						}
444 					} else if (nextObject instanceof Quantity) {
445 						BigDecimal value = ((Quantity) nextObject).getValue();
446 						if (value != null) {
447 							addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
448 						}
449 					} else if (nextObject instanceof Range) {
450 						SimpleQuantity low = ((Range) nextObject).getLow();
451 						if (low != null) {
452 							BigDecimal value = low.getValue();
453 							if (value != null) {
454 								addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
455 							}
456 						}
457 					} else {
458 						if (!multiType) {
459 							throw new ConfigurationException("Search param " + nextSpName + " is of unexpected datatype: " + nextObject.getClass());
460 						}
461 					}
462 				}
463 			}
464 		}
465 
466 		return retVal;
467 	}
468 
469 	/*
470 	 * (non-Javadoc)
471 	 *
472 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamTokens(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
473 	 */
474 	@Override
475 	public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
476 		HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<>();
477 
478 		String useSystem = null;
479 		if (theResource instanceof CodeSystem) {
480 			CodeSystem cs = (CodeSystem) theResource;
481 			useSystem = cs.getUrl();
482 		}
483 
484 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
485 		for (RuntimeSearchParam nextSpDef : searchParams) {
486 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
487 				continue;
488 			}
489 
490 			String resourceType = theEntity.getResourceType();
491 			String nextPath = nextSpDef.getPath();
492 			if (isBlank(nextPath)) {
493 				continue;
494 			}
495 
496 			boolean multiType = false;
497 			if (nextPath.endsWith("[x]")) {
498 				multiType = true;
499 			}
500 
501 			List<String> systems = new ArrayList<>();
502 			List<String> codes = new ArrayList<>();
503 
504 			for (Object nextObject : extractValues(nextPath, theResource)) {
505 
506 				if (nextObject == null) {
507 					continue;
508 				}
509 
510 				// Patient:language
511 				if (nextObject instanceof PatientCommunicationComponent) {
512 					PatientCommunicationComponent nextValue = (PatientCommunicationComponent) nextObject;
513 					nextObject = nextValue.getLanguage();
514 				}
515 
516 				if (nextObject instanceof Identifier) {
517 					Identifier nextValue = (Identifier) nextObject;
518 					if (nextValue.isEmpty()) {
519 						continue;
520 					}
521 					String system = StringUtils.defaultIfBlank(nextValue.getSystemElement().getValueAsString(), null);
522 					String value = nextValue.getValueElement().getValue();
523 					if (isNotBlank(value)) {
524 						systems.add(system);
525 						codes.add(value);
526 					}
527 
528 					if (isNotBlank(nextValue.getType().getText())) {
529 						addStringParam(theEntity, retVal, nextSpDef, nextValue.getType().getText());
530 					}
531 
532 				} else if (nextObject instanceof ContactPoint) {
533 					ContactPoint nextValue = (ContactPoint) nextObject;
534 					if (nextValue.isEmpty()) {
535 						continue;
536 					}
537 					systems.add(nextValue.getSystemElement().getValueAsString());
538 					codes.add(nextValue.getValueElement().getValue());
539 				} else if (nextObject instanceof Enumeration<?>) {
540 					Enumeration<?> obj = (Enumeration<?>) nextObject;
541 					String system = extractSystem(obj);
542 					String code = obj.getValueAsString();
543 					if (isNotBlank(code)) {
544 						systems.add(system);
545 						codes.add(code);
546 					}
547 				} else if (nextObject instanceof IPrimitiveType<?>) {
548 					IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
549 					if (nextValue.isEmpty()) {
550 						continue;
551 					}
552 					if ("CodeSystem.concept.code".equals(nextPath)) {
553 						systems.add(useSystem);
554 					} else {
555 						systems.add(null);
556 					}
557 					codes.add(nextValue.getValueAsString());
558 				} else if (nextObject instanceof Coding) {
559 					Coding nextValue = (Coding) nextObject;
560 					extractTokensFromCoding(systems, codes, theEntity, retVal, nextSpDef, nextValue);
561 				} else if (nextObject instanceof CodeableConcept) {
562 					CodeableConcept nextCC = (CodeableConcept) nextObject;
563 					if (!nextCC.getTextElement().isEmpty()) {
564 						addStringParam(theEntity, retVal, nextSpDef, nextCC.getTextElement().getValue());
565 					}
566 
567 					extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
568 				} else if (nextObject instanceof CapabilityStatementRestSecurityComponent) {
569 					// Conformance.security search param points to something kind of useless right now - This should probably
570 					// be fixed.
571 					CapabilityStatementRestSecurityComponent sec = (CapabilityStatementRestSecurityComponent) nextObject;
572 					for (CodeableConcept nextCC : sec.getService()) {
573 						extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
574 					}
575 				} else if (nextObject instanceof LocationPositionComponent) {
576 					ourLog.warn("Position search not currently supported, not indexing location");
577 					continue;
578 				} else if (nextObject instanceof StructureDefinition.StructureDefinitionContextComponent) {
579 					ourLog.warn("StructureDefinition context indexing not currently supported"); // TODO: implement this
580 					continue;
581 				} else if (resourceType.equals("Consent") && nextPath.equals("Consent.source")) {
582 					// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
583 					continue;
584 				} else {
585 					if (!multiType) {
586 						throw new ConfigurationException("Search param " + nextSpDef.getName() + " with path " + nextPath + " is of unexpected datatype: " + nextObject.getClass());
587 					} else {
588 						continue;
589 					}
590 				}
591 			}
592 
593 			assert systems.size() == codes.size() : "Systems contains " + systems + ", codes contains: " + codes;
594 
595 			Set<Pair<String, String>> haveValues = new HashSet<>();
596 			for (int i = 0; i < systems.size(); i++) {
597 				String system = systems.get(i);
598 				String code = codes.get(i);
599 				if (isBlank(system) && isBlank(code)) {
600 					continue;
601 				}
602 
603 				if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
604 					system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
605 				}
606 				if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
607 					code = code.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
608 				}
609 
610 				Pair<String, String> nextPair = Pair.of(system, code);
611 				if (haveValues.contains(nextPair)) {
612 					continue;
613 				}
614 				haveValues.add(nextPair);
615 
616 				ResourceIndexedSearchParamToken nextEntity;
617 				nextEntity = new ResourceIndexedSearchParamToken(nextSpDef.getName(), system, code);
618 				nextEntity.setResource(theEntity);
619 				retVal.add(nextEntity);
620 
621 			}
622 
623 		}
624 
625 		return retVal;
626 	}
627 
628 	@Override
629 	public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
630 		HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<>();
631 
632 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
633 		for (RuntimeSearchParam nextSpDef : searchParams) {
634 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) {
635 				continue;
636 			}
637 
638 			String nextPath = nextSpDef.getPath();
639 			if (isBlank(nextPath)) {
640 				continue;
641 			}
642 
643 			for (Object nextObject : extractValues(nextPath, theResource)) {
644 				if (nextObject == null || ((IBase) nextObject).isEmpty()) {
645 					continue;
646 				}
647 
648 				String resourceName = nextSpDef.getName();
649 				boolean multiType = false;
650 				if (nextPath.endsWith("[x]")) {
651 					multiType = true;
652 				}
653 
654 				if (nextObject instanceof UriType) {
655 					UriType nextValue = (UriType) nextObject;
656 					if (isBlank(nextValue.getValue())) {
657 						continue;
658 					}
659 
660 					ourLog.trace("Adding param: {}, {}", resourceName, nextValue.getValue());
661 
662 					ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(resourceName, nextValue.getValue());
663 
664 					nextEntity.setResource(theEntity);
665 					retVal.add(nextEntity);
666 				} else {
667 					if (!multiType) {
668 						throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
669 					} else {
670 						continue;
671 					}
672 				}
673 			}
674 		}
675 
676 		return retVal;
677 	}
678 
679 	private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConcept theCodeableConcept, ResourceTable theEntity,
680 																 Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
681 		for (Coding nextCoding : theCodeableConcept.getCoding()) {
682 			extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
683 		}
684 	}
685 
686 	private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate,
687 													 RuntimeSearchParam theParameterDef, Coding nextCoding) {
688 		if (nextCoding != null && !nextCoding.isEmpty()) {
689 
690 			String nextSystem = nextCoding.getSystemElement().getValueAsString();
691 			String nextCode = nextCoding.getCodeElement().getValue();
692 			if (isNotBlank(nextSystem) || isNotBlank(nextCode)) {
693 				theSystems.add(nextSystem);
694 				theCodes.add(nextCode);
695 			}
696 
697 			if (!nextCoding.getDisplayElement().isEmpty()) {
698 				addStringParam(theEntity, theListToPopulate, theParameterDef, nextCoding.getDisplayElement().getValue());
699 			}
700 
701 		}
702 	}
703 
704 	/**
705 	 * Override parent because we're using FHIRPath here
706 	 */
707 	@Override
708 	protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
709 		IWorkerContext worker = new org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext(getContext(), myValidationSupport);
710 		FHIRPathEngine fp = new FHIRPathEngine(worker);
711 
712 		List<Object> values = new ArrayList<>();
713 		String[] nextPathsSplit = SPLIT.split(thePaths);
714 		for (String nextPath : nextPathsSplit) {
715 			List<Base> allValues;
716 			try {
717 				allValues = fp.evaluate((Base) theResource, nextPath);
718 			} catch (FHIRException e) {
719 				String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
720 				throw new InternalErrorException(msg, e);
721 			}
722 			if (allValues.isEmpty() == false) {
723 				values.addAll(allValues);
724 			}
725 		}
726 
727 		for (int i = 0; i < values.size(); i++) {
728 			Object nextObject = values.get(i);
729 			if (nextObject instanceof Extension) {
730 				Extension nextExtension = (Extension) nextObject;
731 				nextObject = nextExtension.getValue();
732 				values.set(i, nextObject);
733 			}
734 		}
735 
736 		return values;
737 	}
738 
739 	@VisibleForTesting
740 	void setValidationSupportForTesting(org.hl7.fhir.r4.hapi.ctx.IValidationSupport theValidationSupport) {
741 		myValidationSupport = theValidationSupport;
742 	}
743 
744 	private static <T extends Enum<?>> String extractSystem(Enumeration<T> theBoundCode) {
745 		if (theBoundCode.getValue() != null) {
746 			return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
747 		}
748 		return null;
749 	}
750 
751 }