View Javadoc
1   package ca.uhn.fhir.context;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 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 static org.apache.commons.lang3.StringUtils.*;
24  
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.lang.reflect.Field;
28  import java.lang.reflect.Modifier;
29  import java.lang.reflect.ParameterizedType;
30  import java.lang.reflect.Type;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.HashMap;
34  import java.util.HashSet;
35  import java.util.Iterator;
36  import java.util.LinkedHashMap;
37  import java.util.LinkedList;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Map.Entry;
41  import java.util.Properties;
42  import java.util.Set;
43  import java.util.TreeMap;
44  import java.util.TreeSet;
45  
46  import ca.uhn.fhir.model.api.CodeableConceptElement;
47  import ca.uhn.fhir.model.api.ExtensionDt;
48  import ca.uhn.fhir.model.api.ICodeEnum;
49  import ca.uhn.fhir.model.api.ICompositeDatatype;
50  import ca.uhn.fhir.model.api.ICompositeElement;
51  import ca.uhn.fhir.model.api.IDatatype;
52  import ca.uhn.fhir.model.api.IElement;
53  import ca.uhn.fhir.model.api.IPrimitiveDatatype;
54  import ca.uhn.fhir.model.api.IResource;
55  import ca.uhn.fhir.model.api.IResourceBlock;
56  import ca.uhn.fhir.model.api.IValueSetEnumBinder;
57  import ca.uhn.fhir.model.api.annotation.Block;
58  import ca.uhn.fhir.model.api.annotation.Child;
59  import ca.uhn.fhir.model.api.annotation.DatatypeDef;
60  import ca.uhn.fhir.model.api.annotation.Description;
61  import ca.uhn.fhir.model.api.annotation.Extension;
62  import ca.uhn.fhir.model.api.annotation.ResourceDef;
63  import ca.uhn.fhir.model.api.annotation.SearchParamDefinition;
64  import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
65  import ca.uhn.fhir.model.dstu.composite.ContainedDt;
66  import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
67  import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
68  import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
69  import ca.uhn.fhir.model.primitive.BoundCodeDt;
70  import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt;
71  import ca.uhn.fhir.model.primitive.ICodedDatatype;
72  import ca.uhn.fhir.model.primitive.XhtmlDt;
73  import ca.uhn.fhir.util.ReflectionUtil;
74  
75  class ModelScanner {
76  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
77  
78  	private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>();
79  	private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
80  	private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>();
81  
82  	// private Map<String, RuntimeResourceDefinition>
83  	// myNameToDatatypeDefinitions = new HashMap<String,
84  	// RuntimeDatatypeDefinition>();
85  
86  	private Map<String, String> myNameToResourceType = new HashMap<String, String>();
87  
88  	private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
89  
90  	private Set<Class<? extends IElement>> myScanAlso = new HashSet<Class<? extends IElement>>();
91  
92  	private Set<Class<? extends ICodeEnum>> myScanAlsoCodeTable = new HashSet<Class<? extends ICodeEnum>>();
93  
94  	ModelScanner(Class<? extends IResource> theResourceTypes) throws ConfigurationException {
95  		Set<Class<? extends IElement>> singleton = new HashSet<Class<? extends IElement>>();
96  		singleton.add(theResourceTypes);
97  		init(null, singleton);
98  	}
99  
100 	ModelScanner(Collection<Class<? extends IResource>> theResourceTypes) throws ConfigurationException {
101 		init(null, new HashSet<Class<? extends IElement>>(theResourceTypes));
102 	}
103 
104 	ModelScanner(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theExistingDefinitions, Collection<Class<? extends IResource>> theResourceTypes) throws ConfigurationException {
105 		init(theExistingDefinitions, new HashSet<Class<? extends IElement>>(theResourceTypes));
106 	}
107 
108 	public Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
109 		return myClassToElementDefinitions;
110 	}
111 
112 	public Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
113 		return myIdToResourceDefinition;
114 	}
115 
116 	public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() {
117 		return (myNameToResourceDefinitions);
118 	}
119 
120 	public Map<String, String> getNameToResourceType() {
121 		return myNameToResourceType;
122 	}
123 
124 	public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
125 		return myRuntimeChildUndeclaredExtensionDefinition;
126 	}
127 
128 	private void addScanAlso(Class<? extends IElement> theType) {
129 		if (theType.isInterface()) {
130 			return;
131 		}
132 		myScanAlso.add(theType);
133 	}
134 
135 	private Class<?> determineElementType(Field next) {
136 		Class<?> nextElementType = next.getType();
137 		if (List.class.equals(nextElementType)) {
138 			nextElementType = ReflectionUtil.getGenericCollectionTypeOfField(next);
139 		} else if (Collection.class.isAssignableFrom(nextElementType)) {
140 			throw new ConfigurationException("Field '" + next.getName() + "' in type '" + next.getClass().getCanonicalName() + "' is a Collection - Only java.util.List curently supported");
141 		}
142 		return nextElementType;
143 	}
144 
145 	@SuppressWarnings("unchecked")
146 	private IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
147 		Class<?> bound = getGenericCollectionTypeOfCodedField(theNext);
148 		if (bound == null) {
149 			throw new ConfigurationException("Field '" + theNext + "' has no parameter for " + BoundCodeDt.class.getSimpleName() + " to determine enum type");
150 		}
151 
152 		try {
153 			Field bindingField = bound.getField("VALUESET_BINDER");
154 			return (IValueSetEnumBinder<Enum<?>>) bindingField.get(null);
155 		} catch (IllegalArgumentException e) {
156 			throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
157 		} catch (IllegalAccessException e) {
158 			throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
159 		} catch (NoSuchFieldException e) {
160 			throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
161 		} catch (SecurityException e) {
162 			throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
163 		}
164 	}
165 
166 	private void init(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theExistingDefinitions, Set<Class<? extends IElement>> toScan) {
167 		if (theExistingDefinitions != null) {
168 			myClassToElementDefinitions.putAll(theExistingDefinitions);
169 		}
170 
171 		int startSize = myClassToElementDefinitions.size();
172 		long start = System.currentTimeMillis();
173 
174 		InputStream str = ModelScanner.class.getResourceAsStream("/ca/uhn/fhir/model/dstu/fhirversion.properties");
175 		if (str == null) {
176 			str = ModelScanner.class.getResourceAsStream("ca/uhn/fhir/model/dstu/fhirversion.properties");
177 		}
178 		if (str == null) {
179 			throw new ConfigurationException("Can not find model property file on classpath: " + "/ca/uhn/fhir/model/dstu/model.properties");
180 		}
181 		Properties prop = new Properties();
182 		try {
183 			prop.load(str);
184 			for (Entry<Object, Object> nextEntry : prop.entrySet()) {
185 				String nextKey = nextEntry.getKey().toString();
186 				String nextValue = nextEntry.getValue().toString();
187 
188 				if (!nextKey.startsWith("datatype.")) {
189 					if (nextKey.startsWith("resource.")) {
190 						String resName = nextKey.substring("resource.".length()).toLowerCase();
191 						myNameToResourceType.put(resName, nextValue);
192 					}
193 					continue;
194 				}
195 
196 				try {
197 					@SuppressWarnings("unchecked")
198 					Class<? extends IElement> nextClass = (Class<? extends IElement>) Class.forName((String) nextValue);
199 					if (!IElement.class.isAssignableFrom(nextClass)) {
200 						ourLog.warn("Class is not assignable from " + IElement.class.getSimpleName() + ": " + nextValue);
201 						continue;
202 					}
203 
204 					toScan.add(nextClass);
205 				} catch (ClassNotFoundException e) {
206 					ourLog.warn("Unknown class exception: " + nextValue, e);
207 				}
208 			}
209 		} catch (IOException e) {
210 			throw new ConfigurationException("Failed to load model property file from classpath: " + "/ca/uhn/fhir/model/dstu/model.properties");
211 		}
212 
213 		// toScan.add(DateDt.class);
214 		// toScan.add(CodeDt.class);
215 		// toScan.add(DecimalDt.class);
216 		// toScan.add(AttachmentDt.class);
217 		// toScan.add(ResourceReferenceDt.class);
218 		// toScan.add(QuantityDt.class);
219 
220 		do {
221 			for (Class<? extends IElement> nextClass : toScan) {
222 				scan(nextClass);
223 			}
224 			for (Iterator<Class<? extends IElement>> iter = myScanAlso.iterator(); iter.hasNext();) {
225 				if (myClassToElementDefinitions.containsKey(iter.next())) {
226 					iter.remove();
227 				}
228 			}
229 			toScan.clear();
230 			toScan.addAll(myScanAlso);
231 			myScanAlso.clear();
232 		} while (!toScan.isEmpty());
233 
234 		for (Entry<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> nextEntry : myClassToElementDefinitions.entrySet()) {
235 			if (theExistingDefinitions != null && theExistingDefinitions.containsKey(nextEntry.getKey())) {
236 				continue;
237 			}
238 			BaseRuntimeElementDefinition<?> next = nextEntry.getValue();
239 			next.sealAndInitialize(myClassToElementDefinitions);
240 		}
241 
242 		myRuntimeChildUndeclaredExtensionDefinition = new RuntimeChildUndeclaredExtensionDefinition();
243 		myRuntimeChildUndeclaredExtensionDefinition.sealAndInitialize(myClassToElementDefinitions);
244 
245 		long time = System.currentTimeMillis() - start;
246 		int size = myClassToElementDefinitions.size() - startSize;
247 		ourLog.info("Done scanning FHIR library, found {} model entries in {}ms", size, time);
248 	}
249 
250 	private void scan(Class<? extends IElement> theClass) throws ConfigurationException {
251 		BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
252 		if (existingDef != null) {
253 			return;
254 		}
255 
256 		ResourceDef resourceDefinition = theClass.getAnnotation(ResourceDef.class);
257 		if (resourceDefinition != null) {
258 			if (!IResource.class.isAssignableFrom(theClass)) {
259 				throw new ConfigurationException("Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName());
260 			}
261 			@SuppressWarnings("unchecked")
262 			Class<? extends IResource> resClass = (Class<? extends IResource>) theClass;
263 			scanResource(resClass, resourceDefinition);
264 		}
265 
266 		DatatypeDef datatypeDefinition = theClass.getAnnotation(DatatypeDef.class);
267 		if (datatypeDefinition != null) {
268 			if (ICompositeDatatype.class.isAssignableFrom(theClass)) {
269 				@SuppressWarnings("unchecked")
270 				Class<? extends ICompositeDatatype> resClass = (Class<? extends ICompositeDatatype>) theClass;
271 				scanCompositeDatatype(resClass, datatypeDefinition);
272 			} else if (IPrimitiveDatatype.class.isAssignableFrom(theClass)) {
273 				@SuppressWarnings({ "unchecked" })
274 				Class<? extends IPrimitiveDatatype<?>> resClass = (Class<? extends IPrimitiveDatatype<?>>) theClass;
275 				scanPrimitiveDatatype(resClass, datatypeDefinition);
276 			} else {
277 				throw new ConfigurationException("Resource type contains a @" + DatatypeDef.class.getSimpleName() + " annotation but does not implement " + IDatatype.class.getCanonicalName() + ": " + theClass.getCanonicalName());
278 			}
279 		}
280 
281 		Block blockDefinition = theClass.getAnnotation(Block.class);
282 		if (blockDefinition != null) {
283 			if (IResourceBlock.class.isAssignableFrom(theClass)) {
284 				@SuppressWarnings("unchecked")
285 				Class<? extends IResourceBlock> blockClass = (Class<? extends IResourceBlock>) theClass;
286 				scanBlock(blockClass, blockDefinition);
287 			} else {
288 				throw new ConfigurationException("Type contains a @" + Block.class.getSimpleName() + " annotation but does not implement " + IResourceBlock.class.getCanonicalName() + ": " + theClass.getCanonicalName());
289 			}
290 		}
291 
292 		if (blockDefinition == null && datatypeDefinition == null && resourceDefinition == null) {
293 			throw new ConfigurationException("Resource type does not contain any valid HAPI-FHIR annotations: " + theClass.getCanonicalName());
294 		}
295 	}
296 
297 	private void scanBlock(Class<? extends IResourceBlock> theClass, Block theBlockDefinition) {
298 		ourLog.debug("Scanning resource block class: {}", theClass.getName());
299 
300 		String resourceName = theClass.getCanonicalName();
301 		if (isBlank(resourceName)) {
302 			throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
303 		}
304 
305 		RuntimeResourceBlockDefinition resourceDef = new RuntimeResourceBlockDefinition(resourceName, theClass);
306 		myClassToElementDefinitions.put(theClass, resourceDef);
307 
308 		scanCompositeElementForChildren(theClass, resourceDef);
309 	}
310 
311 	private void scanCompositeDatatype(Class<? extends ICompositeDatatype> theClass, DatatypeDef theDatatypeDefinition) {
312 		ourLog.debug("Scanning resource class: {}", theClass.getName());
313 
314 		RuntimeCompositeDatatypeDefinition resourceDef;
315 		if (theClass.equals(ExtensionDt.class)) {
316 			resourceDef = new RuntimeExtensionDtDefinition(theDatatypeDefinition, theClass);
317 		} else {
318 			resourceDef = new RuntimeCompositeDatatypeDefinition(theDatatypeDefinition, theClass);
319 		}
320 		myClassToElementDefinitions.put(theClass, resourceDef);
321 		scanCompositeElementForChildren(theClass, resourceDef);
322 	}
323 
324 	@SuppressWarnings("unchecked")
325 	private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition) {
326 		Set<String> elementNames = new HashSet<String>();
327 		TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
328 		TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
329 
330 		LinkedList<Class<? extends ICompositeElement>> classes = new LinkedList<Class<? extends ICompositeElement>>();
331 
332 		/*
333 		 * We scan classes for annotated fields in the class but also all of its superclasses
334 		 */
335 		Class<? extends ICompositeElement> current = theClass;
336 		do {
337 			classes.push(current);
338 			if (ICompositeElement.class.isAssignableFrom(current.getSuperclass())) {
339 				current = (Class<? extends ICompositeElement>) current.getSuperclass();
340 			} else {
341 				current = null;
342 			}
343 		} while (current != null);
344 
345 		for (Class<? extends ICompositeElement> next : classes) {
346 			scanCompositeElementForChildren(next, elementNames, orderToElementDef, orderToExtensionDef);
347 		}
348 
349 		// while (orderToElementDef.size() > 0 && orderToElementDef.firstKey() <
350 		// 0) {
351 		// BaseRuntimeDeclaredChildDefinition elementDef =
352 		// orderToElementDef.remove(orderToElementDef.firstKey());
353 		// if (elementDef.getElementName().equals("identifier")) {
354 		// orderToElementDef.put(theIdentifierOrder, elementDef);
355 		// } else {
356 		// throw new ConfigurationException("Don't know how to handle element: "
357 		// + elementDef.getElementName());
358 		// }
359 		// }
360 
361 		TreeSet<Integer> orders = new TreeSet<Integer>();
362 		orders.addAll(orderToElementDef.keySet());
363 		orders.addAll(orderToExtensionDef.keySet());
364 
365 		for (Integer i : orders) {
366 			BaseRuntimeChildDefinition nextChild = orderToElementDef.get(i);
367 			if (nextChild != null) {
368 				theDefinition.addChild(nextChild);
369 			}
370 			BaseRuntimeDeclaredChildDefinition nextExt = orderToExtensionDef.get(i);
371 			if (nextExt != null) {
372 				theDefinition.addExtension((RuntimeChildDeclaredExtensionDefinition) nextExt);
373 			}
374 		}
375 
376 	}
377 
378 	@SuppressWarnings("unchecked")
379 	private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, Set<String> elementNames, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
380 		int baseElementOrder = theOrderToElementDef.isEmpty() ? 0 : theOrderToElementDef.lastEntry().getKey() + 1;
381 
382 		for (Field next : theClass.getDeclaredFields()) {
383 
384 			if (Modifier.isFinal(next.getModifiers())) {
385 				ourLog.trace("Ignoring constant {} on target type {}",  next.getName(), theClass);
386 				continue;
387 			}
388 			
389 			Child childAnnotation = next.getAnnotation(Child.class);
390 			if (childAnnotation == null) {
391 				ourLog.trace("Ignoring non @Child field {} on target type {}",next.getName() , theClass);
392 				continue;
393 			}
394 
395 			Description descriptionAnnotation = next.getAnnotation(Description.class);
396 
397 			TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
398 			Extension extensionAttr = next.getAnnotation(Extension.class);
399 			if (extensionAttr != null) {
400 				orderMap = theOrderToExtensionDef;
401 			}
402 
403 			String elementName = childAnnotation.name();
404 			int order = childAnnotation.order();
405 			if (order == Child.REPLACE_PARENT) {
406 
407 				if (extensionAttr != null) {
408 
409 					for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
410 						BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
411 						if (nextDef instanceof RuntimeChildDeclaredExtensionDefinition) {
412 							if (nextDef.getExtensionUrl().equals(extensionAttr.url())) {
413 								order = nextEntry.getKey();
414 								orderMap.remove(nextEntry.getKey());
415 								elementNames.remove(elementName);
416 								break;
417 							}
418 						}
419 					}
420 					if (order == Child.REPLACE_PARENT) {
421 						throw new ConfigurationException("Field " + next.getName() + "' on target type " + theClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT + ") but no parent element with extension URL " + extensionAttr.url()
422 								+ " could be found on type " + next.getDeclaringClass().getSimpleName());
423 					}
424 
425 				} else {
426 
427 					for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
428 						BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
429 						if (elementName.equals(nextDef.getElementName())) {
430 							order = nextEntry.getKey();
431 							orderMap.remove(nextEntry.getKey());
432 							elementNames.remove(elementName);
433 							break;
434 						}
435 					}
436 					if (order == Child.REPLACE_PARENT) {
437 						throw new ConfigurationException("Field " + next.getName() + "' on target type " + theClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT + ") but no parent element with name " + elementName + " could be found on type "
438 								+ next.getDeclaringClass().getSimpleName());
439 					}
440 
441 				}
442 
443 			}
444 			if (order < 0 && order != Child.ORDER_UNKNOWN) {
445 				throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + next.getName() + "' on target type: " + theClass);
446 			}
447 			if (order != Child.ORDER_UNKNOWN) {
448 				order = order + baseElementOrder;
449 			}
450 			int min = childAnnotation.min();
451 			int max = childAnnotation.max();
452 			/*
453 			 * Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict wityh any
454 			 * given IDs and can be figured out later
455 			 */
456 			while (order == Child.ORDER_UNKNOWN && orderMap.containsKey(order)) {
457 				order--;
458 			}
459 
460 			List<Class<? extends IElement>> choiceTypes = new ArrayList<Class<? extends IElement>>();
461 			for (Class<? extends IElement> nextChoiceType : childAnnotation.type()) {
462 				choiceTypes.add(nextChoiceType);
463 			}
464 
465 			if (orderMap.containsKey(order)) {
466 				throw new ConfigurationException("Detected duplicate field order '" + childAnnotation.order() + "' for element named '" + elementName + "' in type '" + theClass.getCanonicalName() + "'");
467 			}
468 
469 			if (elementNames.contains(elementName)) {
470 				throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '" + theClass.getCanonicalName() + "'");
471 			}
472 
473 			Class<?> nextElementType = determineElementType(next);
474 
475 			if (nextElementType.equals(ContainedDt.class)) {
476 				/*
477 				 * Child is contained resources
478 				 */
479 				RuntimeChildContainedResources def = new RuntimeChildContainedResources(next, childAnnotation, descriptionAnnotation, elementName);
480 				orderMap.put(order, def);
481 
482 			} else if (choiceTypes.size() > 1 && !ResourceReferenceDt.class.isAssignableFrom(nextElementType)) {
483 				/*
484 				 * Child is a choice element
485 				 */
486 				for (Class<? extends IElement> nextType : choiceTypes) {
487 					addScanAlso(nextType);
488 				}
489 				RuntimeChildChoiceDefinition def = new RuntimeChildChoiceDefinition(next, elementName, childAnnotation, descriptionAnnotation, choiceTypes);
490 				orderMap.put(order, def);
491 
492 			} else if (next.getType().equals(ExtensionDt.class)) {
493 
494 				RuntimeChildExtensionDt def = new RuntimeChildExtensionDt(next, elementName, childAnnotation, descriptionAnnotation);
495 				orderMap.put(order, def);
496 				if (IElement.class.isAssignableFrom(nextElementType)) {
497 					addScanAlso((Class<? extends IElement>) nextElementType);
498 				}
499 
500 			} else if (extensionAttr != null) {
501 				/*
502 				 * Child is an extension
503 				 */
504 				Class<? extends IElement> et = (Class<? extends IElement>) nextElementType;
505 				RuntimeChildDeclaredExtensionDefinition def = new RuntimeChildDeclaredExtensionDefinition(next, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et);
506 				orderMap.put(order, def);
507 				if (IElement.class.isAssignableFrom(nextElementType)) {
508 					addScanAlso((Class<? extends IElement>) nextElementType);
509 				}
510 			} else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType)) {
511 				/*
512 				 * Child is a resource reference
513 				 */
514 				List<Class<? extends IResource>> refTypesList = new ArrayList<Class<? extends IResource>>();
515 				for (Class<? extends IElement> nextType : childAnnotation.type()) {
516 					if (IResource.class.isAssignableFrom(nextType) == false) {
517 						throw new ConfigurationException("Field '" + next.getName() + "' in class '" + next.getDeclaringClass().getCanonicalName() + "' is of type " + ResourceReferenceDt.class + " but contains a non-resource type: " + nextType.getCanonicalName());
518 					}
519 					refTypesList.add((Class<? extends IResource>) nextType);
520 					addScanAlso(nextType);
521 				}
522 				RuntimeChildResourceDefinition def = new RuntimeChildResourceDefinition(next, elementName, childAnnotation, descriptionAnnotation, refTypesList);
523 				orderMap.put(order, def);
524 
525 			} else if (IResourceBlock.class.isAssignableFrom(nextElementType)) {
526 				/*
527 				 * Child is a resource block (i.e. a sub-tag within a resource) TODO: do these have a better name
528 				 * according to HL7?
529 				 */
530 
531 				Class<? extends IResourceBlock> blockDef = (Class<? extends IResourceBlock>) nextElementType;
532 				addScanAlso(blockDef);
533 				RuntimeChildResourceBlockDefinition def = new RuntimeChildResourceBlockDefinition(next, childAnnotation, descriptionAnnotation, elementName, blockDef);
534 				orderMap.put(order, def);
535 
536 			} else if (IDatatype.class.equals(nextElementType) || IElement.class.equals(nextElementType)) {
537 
538 				RuntimeChildAny def = new RuntimeChildAny(next, elementName, childAnnotation, descriptionAnnotation);
539 				orderMap.put(order, def);
540 
541 			} else if (IDatatype.class.isAssignableFrom(nextElementType)) {
542 				Class<? extends IDatatype> nextDatatype = (Class<? extends IDatatype>) nextElementType;
543 
544 				addScanAlso(nextDatatype);
545 				BaseRuntimeChildDatatypeDefinition def;
546 				if (IPrimitiveDatatype.class.isAssignableFrom(nextElementType)) {
547 					if (nextElementType.equals(BoundCodeDt.class)) {
548 						IValueSetEnumBinder<Enum<?>> binder = getBoundCodeBinder(next);
549 						def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder);
550 					} else {
551 						def = new RuntimeChildPrimitiveDatatypeDefinition(next, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
552 					}
553 				} else {
554 					if (nextElementType.equals(BoundCodeableConceptDt.class)) {
555 						IValueSetEnumBinder<Enum<?>> binder = getBoundCodeBinder(next);
556 						def = new RuntimeChildCompositeBoundDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder);
557 					} else if (NarrativeDt.class.getSimpleName().equals(nextElementType.getSimpleName())) {
558 						def = new RuntimeChildNarrativeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
559 					} else {
560 						def = new RuntimeChildCompositeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
561 					}
562 				}
563 
564 				CodeableConceptElement concept = next.getAnnotation(CodeableConceptElement.class);
565 				if (concept != null) {
566 					if (!ICodedDatatype.class.isAssignableFrom(nextDatatype)) {
567 						throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is marked as @" + CodeableConceptElement.class.getCanonicalName() + " but type is not a subtype of " + ICodedDatatype.class.getName());
568 					} else {
569 						Class<? extends ICodeEnum> type = concept.type();
570 						myScanAlsoCodeTable.add(type);
571 						def.setCodeType(type);
572 					}
573 				}
574 
575 				orderMap.put(order, def);
576 
577 			} else {
578 				throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a valid child type: " + nextElementType);
579 			}
580 
581 			elementNames.add(elementName);
582 		}
583 	}
584 
585 	private String scanPrimitiveDatatype(Class<? extends IPrimitiveDatatype<?>> theClass, DatatypeDef theDatatypeDefinition) {
586 		ourLog.debug("Scanning resource class: {}", theClass.getName());
587 
588 		String resourceName = theDatatypeDefinition.name();
589 		if (isBlank(resourceName)) {
590 			throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
591 		}
592 
593 		BaseRuntimeElementDefinition<?> resourceDef;
594 		if (theClass.equals(XhtmlDt.class)) {
595 			@SuppressWarnings("unchecked")
596 			Class<XhtmlDt> clazz = (Class<XhtmlDt>) theClass;
597 			resourceDef = new RuntimePrimitiveDatatypeNarrativeDefinition(resourceName, clazz);
598 		} else {
599 			resourceDef = new RuntimePrimitiveDatatypeDefinition(theDatatypeDefinition, theClass);
600 		}
601 		myClassToElementDefinitions.put(theClass, resourceDef);
602 
603 		return resourceName;
604 	}
605 
606 	private String scanResource(Class<? extends IResource> theClass, ResourceDef resourceDefinition) {
607 		ourLog.debug("Scanning resource class: {}", theClass.getName());
608 
609 		boolean primaryNameProvider = true;
610 		String resourceName = resourceDefinition.name();
611 		if (isBlank(resourceName)) {
612 			Class<?> parent = theClass.getSuperclass();
613 			primaryNameProvider = false;
614 			while (parent.equals(Object.class) == false && isBlank(resourceName)) {
615 				ResourceDef nextDef = parent.getAnnotation(ResourceDef.class);
616 				if (nextDef != null) {
617 					resourceName = nextDef.name();
618 				}
619 				parent = parent.getSuperclass();
620 			}
621 			if (isBlank(resourceName)) {
622 				throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name(): " + theClass.getCanonicalName() + " - This is only allowed for types that extend other resource types ");
623 			}
624 		}
625 
626 		// if (myNameToResourceDefinitions.containsKey(resourceName)) {
627 		// if (!myNameToResourceDefinitions.get(resourceName).getImplementingClass().equals(theClass)) {
628 		// // throw new
629 		// // ConfigurationException("Detected duplicate element name '" +
630 		// // resourceName + "' in types '" + theClass.getCanonicalName() +
631 		// // "' and '"
632 		// // +
633 		// // myNameToResourceDefinitions.get(resourceName).getImplementingClass()
634 		// // + "'");
635 		// } else {
636 		// return resourceName;
637 		// }
638 		// }
639 
640 		String resourceId = resourceDefinition.id();
641 		if (isBlank(resourceId)) {
642 			// throw new ConfigurationException("Resource type @" +
643 			// ResourceDef.class.getSimpleName() +
644 			// " annotation contains no resource ID: " +
645 			// theClass.getCanonicalName());
646 		} else {
647 			if (myIdToResourceDefinition.containsKey(resourceId)) {
648 				throw new ConfigurationException("The following resource types have the same ID of '" + resourceId + "' - " + theClass.getCanonicalName() + " and " + myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName());
649 			}
650 		}
651 
652 		RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(resourceName, theClass, resourceDefinition);
653 		myClassToElementDefinitions.put(theClass, resourceDef);
654 		if (primaryNameProvider) {
655 			myNameToResourceDefinitions.put(resourceName, resourceDef);
656 		}
657 		scanCompositeElementForChildren(theClass, resourceDef);
658 
659 		myIdToResourceDefinition.put(resourceId, resourceDef);
660 
661 		scanResourceForSearchParams(theClass, resourceDef);
662 
663 		return resourceName;
664 	}
665 
666 	private void scanResourceForSearchParams(Class<? extends IResource> theClass, RuntimeResourceDefinition theResourceDef) {
667 
668 		Map<String, RuntimeSearchParam> nameToParam = new HashMap<String, RuntimeSearchParam>();
669 		Map<Field, SearchParamDefinition> compositeFields = new LinkedHashMap<Field, SearchParamDefinition>();
670 
671 		for (Field nextField : theClass.getFields()) {
672 			SearchParamDefinition searchParam = nextField.getAnnotation(SearchParamDefinition.class);
673 			if (searchParam != null) {
674 				SearchParamTypeEnum paramType = SearchParamTypeEnum.valueOf(searchParam.type().toUpperCase());
675 				if (paramType == null) {
676 					throw new ConfigurationException("Searc param " + searchParam.name() + " has an invalid type: " + searchParam.type());
677 				}
678 				if (paramType == SearchParamTypeEnum.COMPOSITE) {
679 					compositeFields.put(nextField, searchParam);
680 					continue;
681 				}
682 				RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType);
683 				theResourceDef.addSearchParam(param);
684 				nameToParam.put(param.getName(), param);
685 			}
686 		}
687 
688 		for (Entry<Field, SearchParamDefinition> nextEntry : compositeFields.entrySet()) {
689 			SearchParamDefinition searchParam = nextEntry.getValue();
690 
691 			List<RuntimeSearchParam> compositeOf = new ArrayList<RuntimeSearchParam>();
692 			for (String nextName : searchParam.compositeOf()) {
693 				RuntimeSearchParam param = nameToParam.get(nextName);
694 				if (param == null) {
695 					ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}", new Object[] { theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet() });
696 					continue;
697 				}
698 				compositeOf.add(param);
699 			}
700 
701 			RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), SearchParamTypeEnum.COMPOSITE, compositeOf);
702 			theResourceDef.addSearchParam(param);
703 		}
704 	}
705 
706 	private static Class<?> getGenericCollectionTypeOfCodedField(Field next) {
707 		Class<?> type;
708 		ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
709 		Type firstArg = collectionType.getActualTypeArguments()[0];
710 		if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
711 			ParameterizedType pt = ((ParameterizedType) firstArg);
712 			firstArg = pt.getActualTypeArguments()[0];
713 			type = (Class<?>) firstArg;
714 		} else {
715 			type = (Class<?>) firstArg;
716 		}
717 		return type;
718 	}
719 
720 }