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