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