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