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