View Javadoc
1   package ca.uhn.fhir.jpa.dao;
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.RuntimeSearchParam;
25  import ca.uhn.fhir.jpa.entity.*;
26  import ca.uhn.fhir.model.api.IDatatype;
27  import ca.uhn.fhir.model.api.IPrimitiveDatatype;
28  import ca.uhn.fhir.model.api.IValueSetEnumBinder;
29  import ca.uhn.fhir.model.base.composite.BaseHumanNameDt;
30  import ca.uhn.fhir.model.dstu2.composite.*;
31  import ca.uhn.fhir.model.dstu2.resource.Conformance.RestSecurity;
32  import ca.uhn.fhir.model.dstu2.resource.Location;
33  import ca.uhn.fhir.model.dstu2.resource.Patient;
34  import ca.uhn.fhir.model.dstu2.resource.Patient.Communication;
35  import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
36  import ca.uhn.fhir.model.dstu2.resource.ValueSet;
37  import ca.uhn.fhir.model.dstu2.valueset.RestfulSecurityServiceEnum;
38  import ca.uhn.fhir.model.primitive.*;
39  import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
40  import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
41  import ca.uhn.fhir.util.FhirTerser;
42  import org.apache.commons.lang3.StringUtils;
43  import org.apache.commons.lang3.tuple.Pair;
44  import org.hl7.fhir.instance.model.api.IBaseDatatype;
45  import org.hl7.fhir.instance.model.api.IBaseExtension;
46  import org.hl7.fhir.instance.model.api.IBaseResource;
47  
48  import javax.measure.quantity.Quantity;
49  import javax.measure.unit.NonSI;
50  import javax.measure.unit.Unit;
51  import java.math.BigDecimal;
52  import java.util.*;
53  
54  import static org.apache.commons.lang3.StringUtils.isBlank;
55  import static org.apache.commons.lang3.StringUtils.isNotBlank;
56  
57  public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implements ISearchParamExtractor {
58  
59  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorDstu2.class);
60  
61  	private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
62  		if (isBlank(searchTerm)) {
63  			return;
64  		}
65  		if (searchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
66  			searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
67  		}
68  
69  		ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), resourceName, BaseHapiFhirDao.normalizeString(searchTerm), searchTerm);
70  		nextEntity.setResource(theEntity);
71  		retVal.add(nextEntity);
72  	}
73  
74  	private void addStringParam(ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> retVal, RuntimeSearchParam nextSpDef, String value) {
75  		if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
76  			value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
77  		}
78  		ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), nextSpDef.getName(), BaseHapiFhirDao.normalizeString(value), value);
79  		nextEntity.setResource(theEntity);
80  		retVal.add(nextEntity);
81  	}
82  
83  	@Override
84  	public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
85  		// TODO: implement
86  		return Collections.emptySet();
87  	}
88  
89  	/*
90  	 * (non-Javadoc)
91  	 *
92  	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamDates(ca.uhn.fhir.jpa.entity.ResourceTable,
93  	 * ca.uhn.fhir.model.api.IResource)
94  	 */
95  	@Override
96  	public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
97  		HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
98  
99  		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
100 		for (RuntimeSearchParam nextSpDef : searchParams) {
101 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
102 				continue;
103 			}
104 
105 			String nextPath = nextSpDef.getPath();
106 			if (isBlank(nextPath)) {
107 				continue;
108 			}
109 
110 			boolean multiType = false;
111 			if (nextPath.endsWith("[x]")) {
112 				multiType = true;
113 			}
114 
115 			for (Object nextObject : extractValues(nextPath, theResource)) {
116 				if (nextObject == null) {
117 					continue;
118 				}
119 
120 				ResourceIndexedSearchParamDate nextEntity;
121 				if (nextObject instanceof BaseDateTimeDt) {
122 					BaseDateTimeDt nextValue = (BaseDateTimeDt) nextObject;
123 					if (nextValue.isEmpty()) {
124 						continue;
125 					}
126 					nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
127 				} else if (nextObject instanceof PeriodDt) {
128 					PeriodDt nextValue = (PeriodDt) nextObject;
129 					if (nextValue.isEmpty()) {
130 						continue;
131 					}
132 					nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
133 				} else {
134 					if (!multiType) {
135 						throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
136 					} else {
137 						continue;
138 					}
139 				}
140 				if (nextEntity != null) {
141 					nextEntity.setResource(theEntity);
142 					retVal.add(nextEntity);
143 				}
144 			}
145 		}
146 
147 		return retVal;
148 	}
149 
150 	/*
151 	 * (non-Javadoc)
152 	 *
153 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamNumber(ca.uhn.fhir.jpa.entity.ResourceTable,
154 	 * ca.uhn.fhir.model.api.IResource)
155 	 */
156 	@Override
157 	public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
158 		HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>();
159 
160 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
161 		for (RuntimeSearchParam nextSpDef : searchParams) {
162 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
163 				continue;
164 			}
165 
166 			String nextPath = nextSpDef.getPath();
167 			if (isBlank(nextPath)) {
168 				continue;
169 			}
170 
171 			for (Object nextObject : extractValues(nextPath, theResource)) {
172 				if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
173 					continue;
174 				}
175 
176 				String resourceName = nextSpDef.getName();
177 				boolean multiType = false;
178 				if (nextPath.endsWith("[x]")) {
179 					multiType = true;
180 				}
181 
182 				if (nextObject instanceof DurationDt) {
183 					DurationDt nextValue = (DurationDt) nextObject;
184 					if (nextValue.getValueElement().isEmpty()) {
185 						continue;
186 					}
187 
188 					if (new UriDt(BaseHapiFhirDao.UCUM_NS).equals(nextValue.getSystemElement())) {
189 						if (isNotBlank(nextValue.getCode())) {
190 
191 							Unit<? extends Quantity> unit = Unit.valueOf(nextValue.getCode());
192 							javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
193 							double dayValue = dayConverter.convert(nextValue.getValue().doubleValue());
194 							DurationDt newValue = new DurationDt();
195 							newValue.setSystem(BaseHapiFhirDao.UCUM_NS);
196 							newValue.setCode(NonSI.DAY.toString());
197 							newValue.setValue(dayValue);
198 							nextValue = newValue;
199 
200 							/*
201 							 * @SuppressWarnings("unchecked") PhysicsUnit<? extends
202 							 * org.unitsofmeasurement.quantity.Quantity<?>> unit = (PhysicsUnit<? extends
203 							 * org.unitsofmeasurement.quantity.Quantity<?>>)
204 							 * UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null); if
205 							 * (unit.isCompatible(UCUM.DAY)) {
206 							 *
207 							 * @SuppressWarnings("unchecked") PhysicsUnit<org.unitsofmeasurement.quantity.Time> timeUnit =
208 							 * (PhysicsUnit<Time>) unit; UnitConverter conv = timeUnit.getConverterTo(UCUM.DAY); double
209 							 * dayValue = conv.convert(nextValue.getValue().getValue().doubleValue()); DurationDt newValue =
210 							 * new DurationDt(); newValue.setSystem(UCUM_NS); newValue.setCode(UCUM.DAY.getSymbol());
211 							 * newValue.setValue(dayValue); nextValue=newValue; }
212 							 */
213 						}
214 					}
215 
216 					ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
217 					nextEntity.setResource(theEntity);
218 					retVal.add(nextEntity);
219 				} else if (nextObject instanceof QuantityDt) {
220 					QuantityDt nextValue = (QuantityDt) nextObject;
221 					if (nextValue.getValueElement().isEmpty()) {
222 						continue;
223 					}
224 
225 					ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
226 					nextEntity.setResource(theEntity);
227 					retVal.add(nextEntity);
228 				} else if (nextObject instanceof IntegerDt) {
229 					IntegerDt nextValue = (IntegerDt) nextObject;
230 					if (nextValue.getValue() == null) {
231 						continue;
232 					}
233 
234 					ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, new BigDecimal(nextValue.getValue()));
235 					nextEntity.setResource(theEntity);
236 					retVal.add(nextEntity);
237 				} else if (nextObject instanceof DecimalDt) {
238 					DecimalDt nextValue = (DecimalDt) nextObject;
239 					if (nextValue.getValue() == null) {
240 						continue;
241 					}
242 
243 					ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
244 					nextEntity.setResource(theEntity);
245 					retVal.add(nextEntity);
246 				} else {
247 					if (!multiType) {
248 						throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
249 					} else {
250 						continue;
251 					}
252 				}
253 			}
254 		}
255 
256 		return retVal;
257 	}
258 
259 	/*
260 	 * (non-Javadoc)
261 	 *
262 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamQuantity(ca.uhn.fhir.jpa.entity.ResourceTable,
263 	 * ca.uhn.fhir.model.api.IResource)
264 	 */
265 	@Override
266 	public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
267 		HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>();
268 
269 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
270 		for (RuntimeSearchParam nextSpDef : searchParams) {
271 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
272 				continue;
273 			}
274 
275 			String nextPath = nextSpDef.getPath();
276 			if (isBlank(nextPath)) {
277 				continue;
278 			}
279 
280 			for (Object nextObject : extractValues(nextPath, theResource)) {
281 				if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
282 					continue;
283 				}
284 
285 				String resourceName = nextSpDef.getName();
286 				boolean multiType = false;
287 				if (nextPath.endsWith("[x]")) {
288 					multiType = true;
289 				}
290 
291 				if (nextObject instanceof QuantityDt) {
292 					QuantityDt nextValue = (QuantityDt) nextObject;
293 					if (nextValue.getValueElement().isEmpty()) {
294 						continue;
295 					}
296 
297 					ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValue.getValueElement().getValue(), nextValue.getSystemElement().getValueAsString(), nextValue.getCode());
298 					nextEntity.setResource(theEntity);
299 					retVal.add(nextEntity);
300 				} else {
301 					if (!multiType) {
302 						throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
303 					} else {
304 						continue;
305 					}
306 				}
307 			}
308 		}
309 
310 		return retVal;
311 	}
312 
313 	/*
314 	 * (non-Javadoc)
315 	 *
316 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamStrings(ca.uhn.fhir.jpa.entity.ResourceTable,
317 	 * ca.uhn.fhir.model.api.IResource)
318 	 */
319 	@Override
320 	public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
321 		HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>();
322 
323 		String resourceName = getContext().getResourceDefinition(theResource).getName();
324 
325 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
326 		for (RuntimeSearchParam nextSpDef : searchParams) {
327 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
328 				continue;
329 			}
330 
331 			String nextPath = nextSpDef.getPath();
332 			String nextSpName = nextSpDef.getName();
333 
334 			if (isBlank(nextPath)) {
335 
336 				// TODO: implement phonetic, and any others that have no path
337 
338 				if ("Questionnaire".equals(resourceName) && nextSpDef.getName().equals("title")) {
339 					Questionnaire q = (Questionnaire) theResource;
340 					String title = q.getGroup().getTitle();
341 					addSearchTerm(theEntity, retVal, nextSpName, title);
342 				}
343 				continue;
344 			}
345 
346 			for (Object nextObject : extractValues(nextPath, theResource)) {
347 				if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
348 					continue;
349 				}
350 
351 				boolean multiType = false;
352 				if (nextPath.endsWith("[x]")) {
353 					multiType = true;
354 				}
355 
356 				if (nextObject instanceof IPrimitiveDatatype<?>) {
357 					IPrimitiveDatatype<?> nextValue = (IPrimitiveDatatype<?>) nextObject;
358 					String searchTerm = nextValue.getValueAsString();
359 					addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
360 				} else {
361 					if (nextObject instanceof BaseHumanNameDt) {
362 						ArrayList<StringDt> allNames = new ArrayList<StringDt>();
363 						HumanNameDt nextHumanName = (HumanNameDt) nextObject;
364 						allNames.addAll(nextHumanName.getFamily());
365 						allNames.addAll(nextHumanName.getGiven());
366 						for (StringDt nextName : allNames) {
367 							addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
368 						}
369 					} else if (nextObject instanceof AddressDt) {
370 						ArrayList<StringDt> allNames = new ArrayList<StringDt>();
371 						AddressDt nextAddress = (AddressDt) nextObject;
372 						allNames.addAll(nextAddress.getLine());
373 						allNames.add(nextAddress.getCityElement());
374 						allNames.add(nextAddress.getStateElement());
375 						allNames.add(nextAddress.getCountryElement());
376 						allNames.add(nextAddress.getPostalCodeElement());
377 						for (StringDt nextName : allNames) {
378 							addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
379 						}
380 					} else if (nextObject instanceof ContactPointDt) {
381 						ContactPointDt nextContact = (ContactPointDt) nextObject;
382 						if (nextContact.getValueElement().isEmpty() == false) {
383 							addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue());
384 						}
385 					} else {
386 						if (!multiType) {
387 							throw new ConfigurationException("Search param " + nextSpName + " is of unexpected datatype: " + nextObject.getClass());
388 						}
389 					}
390 				}
391 			}
392 		}
393 
394 		return retVal;
395 	}
396 
397 	/*
398 	 * (non-Javadoc)
399 	 *
400 	 * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamTokens(ca.uhn.fhir.jpa.entity.ResourceTable,
401 	 * ca.uhn.fhir.model.api.IResource)
402 	 */
403 	@Override
404 	public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
405 		HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<BaseResourceIndexedSearchParam>();
406 
407 		String useSystem = null;
408 		if (theResource instanceof ValueSet) {
409 			ValueSet vs = (ValueSet) theResource;
410 			useSystem = vs.getCodeSystem().getSystem();
411 		}
412 
413 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
414 		for (RuntimeSearchParam nextSpDef : searchParams) {
415 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
416 				continue;
417 			}
418 
419 			String nextPath = nextSpDef.getPath();
420 			if (isBlank(nextPath)) {
421 				continue;
422 			}
423 
424 			boolean multiType = false;
425 			if (nextPath.endsWith("[x]")) {
426 				multiType = true;
427 			}
428 
429 			List<String> systems = new ArrayList<String>();
430 			List<String> codes = new ArrayList<String>();
431 
432 			String needContactPointSystem = null;
433 			if (nextPath.endsWith("(system=phone)")) {
434 				nextPath = nextPath.substring(0, nextPath.length() - "(system=phone)".length());
435 				needContactPointSystem = "phone";
436 			}
437 			if (nextPath.endsWith("(system=email)")) {
438 				nextPath = nextPath.substring(0, nextPath.length() - "(system=email)".length());
439 				needContactPointSystem = "email";
440 			}
441 
442 			for (Object nextObject : extractValues(nextPath, theResource)) {
443 
444 				// Patient:language
445 				if (nextObject instanceof Patient.Communication) {
446 					Communication nextValue = (Patient.Communication) nextObject;
447 					nextObject = nextValue.getLanguage();
448 				}
449 
450 				if (nextObject instanceof IdentifierDt) {
451 					IdentifierDt nextValue = (IdentifierDt) nextObject;
452 					if (nextValue.isEmpty()) {
453 						continue;
454 					}
455 					String system = StringUtils.defaultIfBlank(nextValue.getSystemElement().getValueAsString(), null);
456 					String value = nextValue.getValueElement().getValue();
457 					if (isNotBlank(value)) {
458 						systems.add(system);
459 						codes.add(value);
460 					}
461 
462 					if (isNotBlank(nextValue.getType().getText())) {
463 						addStringParam(theEntity, retVal, nextSpDef, nextValue.getType().getText());
464 					}
465 
466 				} else if (nextObject instanceof ContactPointDt) {
467 					ContactPointDt nextValue = (ContactPointDt) nextObject;
468 					if (nextValue.isEmpty()) {
469 						continue;
470 					}
471 					if (isNotBlank(needContactPointSystem)) {
472 						if (!needContactPointSystem.equals(nextValue.getSystemElement().getValueAsString())) {
473 							continue;
474 						}
475 					}
476 					systems.add(nextValue.getSystemElement().getValueAsString());
477 					codes.add(nextValue.getValueElement().getValue());
478 				} else if (nextObject instanceof BoundCodeDt) {
479 					BoundCodeDt<?> obj = (BoundCodeDt<?>) nextObject;
480 					String system = extractSystem(obj);
481 					String code = obj.getValue();
482 					if (isNotBlank(code)) {
483 						systems.add(system);
484 						codes.add(code);
485 					}
486 				} else if (nextObject instanceof IPrimitiveDatatype<?>) {
487 					IPrimitiveDatatype<?> nextValue = (IPrimitiveDatatype<?>) nextObject;
488 					if (nextValue.isEmpty()) {
489 						continue;
490 					}
491 					if ("ValueSet.codeSystem.concept.code".equals(nextPath)) {
492 						systems.add(useSystem);
493 					} else {
494 						systems.add(null);
495 					}
496 					codes.add(nextValue.getValueAsString());
497 				} else if (nextObject instanceof CodingDt) {
498 					CodingDt nextValue = (CodingDt) nextObject;
499 					extractTokensFromCoding(systems, codes, theEntity, retVal, nextSpDef, nextValue);
500 				} else if (nextObject instanceof CodeableConceptDt) {
501 					CodeableConceptDt nextCC = (CodeableConceptDt) nextObject;
502 					if (!nextCC.getTextElement().isEmpty()) {
503 						addStringParam(theEntity, retVal, nextSpDef, nextCC.getTextElement().getValue());
504 					}
505 
506 					extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
507 				} else if (nextObject instanceof RestSecurity) {
508 					// Conformance.security search param points to something kind of useless right now - This should probably
509 					// be fixed.
510 					RestSecurity sec = (RestSecurity) nextObject;
511 					for (BoundCodeableConceptDt<RestfulSecurityServiceEnum> nextCC : sec.getService()) {
512 						extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
513 					}
514 				} else if (nextObject instanceof Location.Position) {
515 					ourLog.warn("Position search not currently supported, not indexing location");
516 					continue;
517 				} else {
518 					if (!multiType) {
519 						throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
520 					} else {
521 						continue;
522 					}
523 				}
524 			}
525 
526 			assert systems.size() == codes.size() : "Systems contains " + systems + ", codes contains: " + codes;
527 
528 			Set<Pair<String, String>> haveValues = new HashSet<Pair<String, String>>();
529 			for (int i = 0; i < systems.size(); i++) {
530 				String system = systems.get(i);
531 				String code = codes.get(i);
532 				if (isBlank(system) && isBlank(code)) {
533 					continue;
534 				}
535 
536 				if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
537 					system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
538 				}
539 				if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
540 					code = code.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
541 				}
542 
543 				Pair<String, String> nextPair = Pair.of(system, code);
544 				if (haveValues.contains(nextPair)) {
545 					continue;
546 				}
547 				haveValues.add(nextPair);
548 
549 				ResourceIndexedSearchParamToken nextEntity;
550 				nextEntity = new ResourceIndexedSearchParamToken(nextSpDef.getName(), system, code);
551 				nextEntity.setResource(theEntity);
552 				retVal.add(nextEntity);
553 
554 			}
555 
556 		}
557 
558 		return retVal;
559 	}
560 
561 	@Override
562 	public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
563 		HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<ResourceIndexedSearchParamUri>();
564 
565 		Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
566 		for (RuntimeSearchParam nextSpDef : searchParams) {
567 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) {
568 				continue;
569 			}
570 
571 			String nextPath = nextSpDef.getPath();
572 			if (isBlank(nextPath)) {
573 				continue;
574 			}
575 
576 			for (Object nextObject : extractValues(nextPath, theResource)) {
577 				if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
578 					continue;
579 				}
580 
581 				String resourceName = nextSpDef.getName();
582 				boolean multiType = false;
583 				if (nextPath.endsWith("[x]")) {
584 					multiType = true;
585 				}
586 
587 				if (nextObject instanceof UriDt) {
588 					UriDt nextValue = (UriDt) nextObject;
589 					if (isBlank(nextValue.getValue())) {
590 						continue;
591 					}
592 
593 					ourLog.trace("Adding param: {}, {}", resourceName, nextValue.getValue());
594 
595 					ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(resourceName, nextValue.getValue());
596 
597 					nextEntity.setResource(theEntity);
598 					retVal.add(nextEntity);
599 				} else {
600 					if (!multiType) {
601 						throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
602 					} else {
603 						continue;
604 					}
605 				}
606 			}
607 		}
608 
609 		return retVal;
610 	}
611 
612 
613 	private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConceptDt theCodeableConcept, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
614 		for (CodingDt nextCoding : theCodeableConcept.getCoding()) {
615 			extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
616 		}
617 	}
618 
619 	private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef, CodingDt nextCoding) {
620 		if (nextCoding != null && !nextCoding.isEmpty()) {
621 
622 			String nextSystem = nextCoding.getSystemElement().getValueAsString();
623 			String nextCode = nextCoding.getCodeElement().getValue();
624 			if (isNotBlank(nextSystem) || isNotBlank(nextCode)) {
625 				theSystems.add(nextSystem);
626 				theCodes.add(nextCode);
627 			}
628 
629 			if (!nextCoding.getDisplayElement().isEmpty()) {
630 				addStringParam(theEntity, theListToPopulate, theParameterDef, nextCoding.getDisplayElement().getValue());
631 			}
632 
633 		}
634 	}
635 
636 	@Override
637 	protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
638 		List<Object> values = new ArrayList<>();
639 		String[] nextPathsSplit = SPLIT.split(thePaths);
640 		FhirTerser t = getContext().newTerser();
641 		for (String nextPath : nextPathsSplit) {
642 			String nextPathTrimmed = nextPath.trim();
643 			List<Object> allValues;
644 			try {
645 				allValues = t.getValues(theResource, nextPathTrimmed);
646 			} catch (Exception e) {
647 				String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
648 				throw new InternalErrorException(msg, e);
649 			}
650 			for (Object next : allValues) {
651 				if (next instanceof IBaseExtension) {
652 					IBaseDatatype value = ((IBaseExtension) next).getValue();
653 					if (value != null) {
654 						values.add(value);
655 					}
656 				} else {
657 					values.add(next);
658 				}
659 			}
660 		}
661 		return values;
662 	}
663 
664 
665 	private static <T extends Enum<?>> String extractSystem(BoundCodeDt<T> theBoundCode) {
666 		if (theBoundCode.getValueAsEnum() != null) {
667 			IValueSetEnumBinder<T> binder = theBoundCode.getBinder();
668 			return binder.toSystemString(theBoundCode.getValueAsEnum());
669 		}
670 		return null;
671 	}
672 
673 }