1 package ca.uhn.fhir.context;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
83
84
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
214
215
216
217
218
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
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
350
351
352
353
354
355
356
357
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
454
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
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
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
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
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
528
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
627
628
629
630
631
632
633
634
635
636
637
638
639
640 String resourceId = resourceDefinition.id();
641 if (isBlank(resourceId)) {
642
643
644
645
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 }