View Javadoc
1   package ca.uhn.fhir.context;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2019 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.RuntimeSearchParam.RuntimeSearchParamStatusEnum;
24  import ca.uhn.fhir.model.api.*;
25  import ca.uhn.fhir.model.api.annotation.*;
26  import ca.uhn.fhir.model.primitive.BoundCodeDt;
27  import ca.uhn.fhir.model.primitive.XhtmlDt;
28  import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
29  import ca.uhn.fhir.util.ReflectionUtil;
30  import org.hl7.fhir.instance.model.api.*;
31  
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.lang.annotation.Annotation;
35  import java.lang.reflect.AnnotatedElement;
36  import java.lang.reflect.Field;
37  import java.lang.reflect.ParameterizedType;
38  import java.lang.reflect.Type;
39  import java.util.*;
40  import java.util.Map.Entry;
41  
42  import static org.apache.commons.lang3.StringUtils.isBlank;
43  
44  class ModelScanner {
45  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
46  
47  	private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
48  	private FhirContext myContext;
49  	private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
50  	private Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinitions = new HashMap<String, BaseRuntimeElementDefinition<?>>();
51  	private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>();
52  	private Map<String, Class<? extends IBaseResource>> myNameToResourceType = new HashMap<String, Class<? extends IBaseResource>>();
53  	private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
54  	private Set<Class<? extends IBase>> myScanAlso = new HashSet<Class<? extends IBase>>();
55  	private FhirVersionEnum myVersion;
56  
57  	private Set<Class<? extends IBase>> myVersionTypes;
58  
59  	ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions,
60  					 Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
61  		myContext = theContext;
62  		myVersion = theVersion;
63  		Set<Class<? extends IBase>> toScan;
64  		if (theResourceTypes != null) {
65  			toScan = new HashSet<Class<? extends IBase>>(theResourceTypes);
66  		} else {
67  			toScan = new HashSet<Class<? extends IBase>>();
68  		}
69  		init(theExistingDefinitions, toScan);
70  	}
71  
72  	public Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
73  		return myClassToElementDefinitions;
74  	}
75  
76  	public Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
77  		return myIdToResourceDefinition;
78  	}
79  
80  	public Map<String, BaseRuntimeElementDefinition<?>> getNameToElementDefinitions() {
81  		return myNameToElementDefinitions;
82  	}
83  
84  	public Map<String, RuntimeResourceDefinition> getNameToResourceDefinition() {
85  		return myNameToResourceDefinitions;
86  	}
87  
88  	public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() {
89  		return (myNameToResourceDefinitions);
90  	}
91  
92  	public Map<String, Class<? extends IBaseResource>> getNameToResourceType() {
93  		return myNameToResourceType;
94  	}
95  
96  	public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
97  		return myRuntimeChildUndeclaredExtensionDefinition;
98  	}
99  
100 	private void init(Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions, Set<Class<? extends IBase>> theTypesToScan) {
101 		if (theExistingDefinitions != null) {
102 			myClassToElementDefinitions.putAll(theExistingDefinitions);
103 		}
104 
105 		int startSize = myClassToElementDefinitions.size();
106 		long start = System.currentTimeMillis();
107 		Map<String, Class<? extends IBaseResource>> resourceTypes = myNameToResourceType;
108 
109 		Set<Class<? extends IBase>> typesToScan = theTypesToScan;
110 		myVersionTypes = scanVersionPropertyFile(typesToScan, resourceTypes, myVersion, myClassToElementDefinitions);
111 
112 		do {
113 			for (Class<? extends IBase> nextClass : typesToScan) {
114 				scan(nextClass);
115 			}
116 			myScanAlso.removeIf(theClass -> myClassToElementDefinitions.containsKey(theClass));
117 			typesToScan.clear();
118 			typesToScan.addAll(myScanAlso);
119 			myScanAlso.clear();
120 		} while (!typesToScan.isEmpty());
121 
122 		for (Entry<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> nextEntry : myClassToElementDefinitions.entrySet()) {
123 			if (theExistingDefinitions != null && theExistingDefinitions.containsKey(nextEntry.getKey())) {
124 				continue;
125 			}
126 			BaseRuntimeElementDefinition<?> next = nextEntry.getValue();
127 
128 			boolean deferredSeal = false;
129 			if (myContext.getPerformanceOptions().contains(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING)) {
130 				if (next instanceof BaseRuntimeElementCompositeDefinition) {
131 					deferredSeal = true;
132 				}
133 			}
134 			if (!deferredSeal) {
135 				next.sealAndInitialize(myContext, myClassToElementDefinitions);
136 			}
137 		}
138 
139 		myRuntimeChildUndeclaredExtensionDefinition = new RuntimeChildUndeclaredExtensionDefinition();
140 		myRuntimeChildUndeclaredExtensionDefinition.sealAndInitialize(myContext, myClassToElementDefinitions);
141 
142 		long time = System.currentTimeMillis() - start;
143 		int size = myClassToElementDefinitions.size() - startSize;
144 		ourLog.debug("Done scanning FHIR library, found {} model entries in {}ms", size, time);
145 	}
146 
147 	private boolean isStandardType(Class<? extends IBase> theClass) {
148 		boolean retVal = myVersionTypes.contains(theClass);
149 		return retVal;
150 	}
151 
152 	private void scan(Class<? extends IBase> theClass) throws ConfigurationException {
153 		BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
154 		if (existingDef != null) {
155 			return;
156 		}
157 
158 		ResourceDef resourceDefinition = pullAnnotation(theClass, ResourceDef.class);
159 		if (resourceDefinition != null) {
160 			if (!IBaseResource.class.isAssignableFrom(theClass)) {
161 				throw new ConfigurationException(
162 					"Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName());
163 			}
164 			@SuppressWarnings("unchecked")
165 			Class<? extends IBaseResource> resClass = (Class<? extends IBaseResource>) theClass;
166 			scanResource(resClass, resourceDefinition);
167 			return;
168 		}
169 
170 		DatatypeDef datatypeDefinition = pullAnnotation(theClass, DatatypeDef.class);
171 		if (datatypeDefinition != null) {
172 			if (ICompositeType.class.isAssignableFrom(theClass)) {
173 				@SuppressWarnings("unchecked")
174 				Class<? extends ICompositeType> resClass = (Class<? extends ICompositeType>) theClass;
175 				scanCompositeDatatype(resClass, datatypeDefinition);
176 			} else if (IPrimitiveType.class.isAssignableFrom(theClass)) {
177 				@SuppressWarnings({"unchecked"})
178 				Class<? extends IPrimitiveType<?>> resClass = (Class<? extends IPrimitiveType<?>>) theClass;
179 				scanPrimitiveDatatype(resClass, datatypeDefinition);
180 			}
181 
182 			return;
183 		}
184 
185 		Block blockDefinition = pullAnnotation(theClass, Block.class);
186 
187 		if (blockDefinition != null) {
188 			if (IResourceBlock.class.isAssignableFrom(theClass) || IBaseBackboneElement.class.isAssignableFrom(theClass) || IBaseDatatypeElement.class.isAssignableFrom(theClass)) {
189 				scanBlock(theClass);
190 			} else {
191 				throw new ConfigurationException(
192 					"Type contains a @" + Block.class.getSimpleName() + " annotation but does not implement " + IResourceBlock.class.getCanonicalName() + ": " + theClass.getCanonicalName());
193 			}
194 		}
195 
196 		if (blockDefinition == null
197 //Redundant	checking && datatypeDefinition == null && resourceDefinition == null
198 		) {
199 			throw new ConfigurationException("Resource class[" + theClass.getName() + "] does not contain any valid HAPI-FHIR annotations");
200 		}
201 	}
202 
203 	private void scanBlock(Class<? extends IBase> theClass) {
204 		ourLog.debug("Scanning resource block class: {}", theClass.getName());
205 
206 		String resourceName = theClass.getCanonicalName();
207 		if (isBlank(resourceName)) {
208 			throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
209 		}
210 
211 		// Just in case someone messes up when upgrading from DSTU2
212 		if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
213 			if (BaseIdentifiableElement.class.isAssignableFrom(theClass)) {
214 				throw new ConfigurationException("@Block class for version " + myContext.getVersion().getVersion().name() + " should not extend " + BaseIdentifiableElement.class.getSimpleName() + ": " + theClass.getName());
215 			}
216 		}
217 
218 		RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions);
219 		blockDef.populateScanAlso(myScanAlso);
220 
221 		myClassToElementDefinitions.put(theClass, blockDef);
222 	}
223 
224 	private void scanCompositeDatatype(Class<? extends ICompositeType> theClass, DatatypeDef theDatatypeDefinition) {
225 		ourLog.debug("Scanning datatype class: {}", theClass.getName());
226 
227 		RuntimeCompositeDatatypeDefinition elementDef;
228 		if (theClass.equals(ExtensionDt.class)) {
229 			elementDef = new RuntimeExtensionDtDefinition(theDatatypeDefinition, theClass, true, myContext, myClassToElementDefinitions);
230 			// } else if (IBaseMetaType.class.isAssignableFrom(theClass)) {
231 			// resourceDef = new RuntimeMetaDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
232 		} else {
233 			elementDef = new RuntimeCompositeDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions);
234 		}
235 		myClassToElementDefinitions.put(theClass, elementDef);
236 		myNameToElementDefinitions.put(elementDef.getName().toLowerCase(), elementDef);
237 
238 		/*
239 		 * See #423:
240 		 * If the type contains a field that has a custom type, we want to make
241 		 * sure that this type gets scanned as well
242 		 */
243 		elementDef.populateScanAlso(myScanAlso);
244 	}
245 
246 	private String scanPrimitiveDatatype(Class<? extends IPrimitiveType<?>> theClass, DatatypeDef theDatatypeDefinition) {
247 		ourLog.debug("Scanning resource class: {}", theClass.getName());
248 
249 		String resourceName = theDatatypeDefinition.name();
250 		if (isBlank(resourceName)) {
251 			throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
252 		}
253 
254 		BaseRuntimeElementDefinition<?> elementDef;
255 		if (theClass.equals(XhtmlDt.class)) {
256 			@SuppressWarnings("unchecked")
257 			Class<XhtmlDt> clazz = (Class<XhtmlDt>) theClass;
258 			elementDef = new RuntimePrimitiveDatatypeNarrativeDefinition(resourceName, clazz, isStandardType(clazz));
259 		} else if (IBaseXhtml.class.isAssignableFrom(theClass)) {
260 			@SuppressWarnings("unchecked")
261 			Class<? extends IBaseXhtml> clazz = (Class<? extends IBaseXhtml>) theClass;
262 			elementDef = new RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition(resourceName, clazz, isStandardType(clazz));
263 		} else if (IIdType.class.isAssignableFrom(theClass)) {
264 			elementDef = new RuntimeIdDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
265 		} else {
266 			elementDef = new RuntimePrimitiveDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
267 		}
268 		myClassToElementDefinitions.put(theClass, elementDef);
269 		if (!theDatatypeDefinition.isSpecialization()) {
270 			if (myVersion.isRi() && IDatatype.class.isAssignableFrom(theClass)) {
271 				ourLog.debug("Not adding non RI type {} to RI context", theClass);
272 			} else if (!myVersion.isRi() && !IDatatype.class.isAssignableFrom(theClass)) {
273 				ourLog.debug("Not adding RI type {} to non RI context", theClass);
274 			} else {
275 				myNameToElementDefinitions.put(resourceName, elementDef);
276 			}
277 		}
278 
279 		return resourceName;
280 	}
281 
282 	private String scanResource(Class<? extends IBaseResource> theClass, ResourceDef resourceDefinition) {
283 		ourLog.debug("Scanning resource class: {}", theClass.getName());
284 
285 		boolean primaryNameProvider = true;
286 		String resourceName = resourceDefinition.name();
287 		if (isBlank(resourceName)) {
288 			Class<?> parent = theClass.getSuperclass();
289 			primaryNameProvider = false;
290 			while (parent.equals(Object.class) == false && isBlank(resourceName)) {
291 				ResourceDef nextDef = pullAnnotation(parent, ResourceDef.class);
292 				if (nextDef != null) {
293 					resourceName = nextDef.name();
294 				}
295 				parent = parent.getSuperclass();
296 			}
297 			if (isBlank(resourceName)) {
298 				throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name(): " + theClass.getCanonicalName()
299 					+ " - This is only allowed for types that extend other resource types ");
300 			}
301 		}
302 
303 		String resourceNameLowerCase = resourceName.toLowerCase();
304 		Class<? extends IBaseResource> builtInType = myNameToResourceType.get(resourceNameLowerCase);
305 		boolean standardType = builtInType != null && builtInType.equals(theClass) == true;
306 		if (primaryNameProvider) {
307 			if (builtInType != null && builtInType.equals(theClass) == false) {
308 				primaryNameProvider = false;
309 			}
310 		}
311 
312 		String resourceId = resourceDefinition.id();
313 		if (!isBlank(resourceId)) {
314 			if (myIdToResourceDefinition.containsKey(resourceId)) {
315 				throw new ConfigurationException("The following resource types have the same ID of '" + resourceId + "' - " + theClass.getCanonicalName() + " and "
316 					+ myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName());
317 			}
318 		}
319 
320 		RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(myContext, resourceName, theClass, resourceDefinition, standardType, myClassToElementDefinitions);
321 		myClassToElementDefinitions.put(theClass, resourceDef);
322 		if (primaryNameProvider) {
323 			if (resourceDef.getStructureVersion() == myVersion) {
324 				myNameToResourceDefinitions.put(resourceNameLowerCase, resourceDef);
325 			}
326 		}
327 
328 		myIdToResourceDefinition.put(resourceId, resourceDef);
329 
330 		scanResourceForSearchParams(theClass, resourceDef);
331 
332 		/*
333 		 * See #423:
334 		 * If the type contains a field that has a custom type, we want to make
335 		 * sure that this type gets scanned as well
336 		 */
337 		resourceDef.populateScanAlso(myScanAlso);
338 
339 		return resourceName;
340 	}
341 
342 	private void scanResourceForSearchParams(Class<? extends IBaseResource> theClass, RuntimeResourceDefinition theResourceDef) {
343 
344 		Map<String, RuntimeSearchParam> nameToParam = new HashMap<String, RuntimeSearchParam>();
345 		Map<Field, SearchParamDefinition> compositeFields = new LinkedHashMap<Field, SearchParamDefinition>();
346 
347 		/*
348 		 * Make sure we pick up fields in interfaces too.. This ensures that we
349 		 * grab the _id field which generally gets picked up via interface
350 		 */
351 		Set<Field> fields = new HashSet<Field>(Arrays.asList(theClass.getFields()));
352 		Class<?> nextClass = theClass;
353 		do {
354 			for (Class<?> nextInterface : nextClass.getInterfaces()) {
355 				fields.addAll(Arrays.asList(nextInterface.getFields()));
356 			}
357 			nextClass = nextClass.getSuperclass();
358 		} while (nextClass.equals(Object.class) == false);
359 
360 		/*
361 		 * Now scan the fields for search params
362 		 */
363 		for (Field nextField : fields) {
364 			SearchParamDefinition searchParam = pullAnnotation(nextField, SearchParamDefinition.class);
365 			if (searchParam != null) {
366 				RestSearchParameterTypeEnum paramType = RestSearchParameterTypeEnum.forCode(searchParam.type().toLowerCase());
367 				if (paramType == null) {
368 					throw new ConfigurationException("Search param " + searchParam.name() + " has an invalid type: " + searchParam.type());
369 				}
370 				Set<String> providesMembershipInCompartments;
371 				providesMembershipInCompartments = new HashSet<>();
372 				for (Compartment next : searchParam.providesMembershipIn()) {
373 					if (paramType != RestSearchParameterTypeEnum.REFERENCE) {
374 						StringBuilder b = new StringBuilder();
375 						b.append("Search param ");
376 						b.append(searchParam.name());
377 						b.append(" on resource type ");
378 						b.append(theClass.getName());
379 						b.append(" provides compartment membership but is not of type 'reference'");
380 						ourLog.warn(b.toString());
381 						continue;
382 //						throw new ConfigurationException(b.toString());
383 					}
384 					providesMembershipInCompartments.add(next.name());
385 				}
386 
387 				if (paramType == RestSearchParameterTypeEnum.COMPOSITE) {
388 					compositeFields.put(nextField, searchParam);
389 					continue;
390 				}
391 
392 
393 				Collection<String> base = Collections.singletonList(theResourceDef.getName());
394 				RuntimeSearchParam param = new RuntimeSearchParam(null, null, searchParam.name(), searchParam.description(), searchParam.path(), paramType, null, providesMembershipInCompartments, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE, base);
395 				theResourceDef.addSearchParam(param);
396 				nameToParam.put(param.getName(), param);
397 			}
398 		}
399 
400 		for (Entry<Field, SearchParamDefinition> nextEntry : compositeFields.entrySet()) {
401 			SearchParamDefinition searchParam = nextEntry.getValue();
402 
403 			List<RuntimeSearchParam> compositeOf = new ArrayList<RuntimeSearchParam>();
404 			for (String nextName : searchParam.compositeOf()) {
405 				RuntimeSearchParam param = nameToParam.get(nextName);
406 				if (param == null) {
407 					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: {}",
408 						new Object[]{theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()});
409 					continue;
410 				}
411 				compositeOf.add(param);
412 			}
413 
414 			RuntimeSearchParam param = new RuntimeSearchParam(null, null, searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf, null, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE);
415 			theResourceDef.addSearchParam(param);
416 		}
417 	}
418 
419 	private Set<String> toTargetList(Class<? extends IBaseResource>[] theTarget) {
420 		HashSet<String> retVal = new HashSet<String>();
421 
422 		for (Class<? extends IBaseResource> nextType : theTarget) {
423 			ResourceDef resourceDef = nextType.getAnnotation(ResourceDef.class);
424 			if (resourceDef != null) {
425 				retVal.add(resourceDef.name());
426 			}
427 		}
428 
429 		return retVal;
430 	}
431 
432 	static Class<?> determineElementType(Field next) {
433 		Class<?> nextElementType = next.getType();
434 		if (List.class.equals(nextElementType)) {
435 			nextElementType = ReflectionUtil.getGenericCollectionTypeOfField(next);
436 		} else if (Collection.class.isAssignableFrom(nextElementType)) {
437 			throw new ConfigurationException("Field '" + next.getName() + "' in type '" + next.getClass().getCanonicalName() + "' is a Collection - Only java.util.List curently supported");
438 		}
439 		return nextElementType;
440 	}
441 
442 	@SuppressWarnings("unchecked")
443 	static IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
444 		Class<?> bound = getGenericCollectionTypeOfCodedField(theNext);
445 		if (bound == null) {
446 			throw new ConfigurationException("Field '" + theNext + "' has no parameter for " + BoundCodeDt.class.getSimpleName() + " to determine enum type");
447 		}
448 
449 		String fieldName = "VALUESET_BINDER";
450 		try {
451 			Field bindingField = bound.getField(fieldName);
452 			return (IValueSetEnumBinder<Enum<?>>) bindingField.get(null);
453 		} catch (Exception e) {
454 			throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field (must have a field called " + fieldName + ")", e);
455 		}
456 	}
457 
458 	/**
459 	 * There are two implementations of all of the annotations (e.g. {@link Child} since the HL7.org ones will eventually replace the HAPI
460 	 * ones. Annotations can't extend each other or implement interfaces or anything like that, so rather than duplicate all of the annotation processing code this method just creates an interface
461 	 * Proxy to simulate the HAPI annotations if the HL7.org ones are found instead.
462 	 */
463 	static <T extends Annotation> T pullAnnotation(AnnotatedElement theTarget, Class<T> theAnnotationType) {
464 		T retVal = theTarget.getAnnotation(theAnnotationType);
465 		return retVal;
466 	}
467 
468 	static Class<? extends Enum<?>> determineEnumTypeForBoundField(Field next) {
469 		@SuppressWarnings("unchecked")
470 		Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next);
471 		return enumType;
472 	}
473 
474 	private static Class<?> getGenericCollectionTypeOfCodedField(Field next) {
475 		Class<?> type;
476 		ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
477 		Type firstArg = collectionType.getActualTypeArguments()[0];
478 		if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
479 			ParameterizedType pt = ((ParameterizedType) firstArg);
480 			firstArg = pt.getActualTypeArguments()[0];
481 			type = (Class<?>) firstArg;
482 		} else {
483 			type = (Class<?>) firstArg;
484 		}
485 		return type;
486 	}
487 
488 	static Set<Class<? extends IBase>> scanVersionPropertyFile(Set<Class<? extends IBase>> theDatatypes, Map<String, Class<? extends IBaseResource>> theResourceTypes, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingElementDefinitions) {
489 		Set<Class<? extends IBase>> retVal = new HashSet<Class<? extends IBase>>();
490 
491 		try (InputStream str = theVersion.getVersionImplementation().getFhirVersionPropertiesFile()) {
492 			Properties prop = new Properties();
493 			prop.load(str);
494 			for (Entry<Object, Object> nextEntry : prop.entrySet()) {
495 				String nextKey = nextEntry.getKey().toString();
496 				String nextValue = nextEntry.getValue().toString();
497 
498 				if (nextKey.startsWith("datatype.")) {
499 					if (theDatatypes != null) {
500 						try {
501 							// Datatypes
502 
503 							@SuppressWarnings("unchecked")
504 							Class<? extends IBase> dtType = (Class<? extends IBase>) Class.forName(nextValue);
505 							if (theExistingElementDefinitions.containsKey(dtType)) {
506 								continue;
507 							}
508 							retVal.add(dtType);
509 
510 							if (IElement.class.isAssignableFrom(dtType)) {
511 								@SuppressWarnings("unchecked")
512 								Class<? extends IElement> nextClass = (Class<? extends IElement>) dtType;
513 								theDatatypes.add(nextClass);
514 							} else if (IBaseDatatype.class.isAssignableFrom(dtType)) {
515 								@SuppressWarnings("unchecked")
516 								Class<? extends IBaseDatatype> nextClass = (Class<? extends IBaseDatatype>) dtType;
517 								theDatatypes.add(nextClass);
518 							} else {
519 								ourLog.warn("Class is not assignable from " + IElement.class.getSimpleName() + " or " + IBaseDatatype.class.getSimpleName() + ": " + nextValue);
520 								continue;
521 							}
522 
523 						} catch (ClassNotFoundException e) {
524 							throw new ConfigurationException("Unknown class[" + nextValue + "] for data type definition: " + nextKey.substring("datatype.".length()), e);
525 						}
526 					}
527 				} else if (nextKey.startsWith("resource.")) {
528 					// Resources
529 					String resName = nextKey.substring("resource.".length()).toLowerCase();
530 					try {
531 						@SuppressWarnings("unchecked")
532 						Class<? extends IBaseResource> nextClass = (Class<? extends IBaseResource>) Class.forName(nextValue);
533 						if (theExistingElementDefinitions.containsKey(nextClass)) {
534 							continue;
535 						}
536 						if (!IBaseResource.class.isAssignableFrom(nextClass)) {
537 							throw new ConfigurationException("Class is not assignable from " + IBaseResource.class.getSimpleName() + ": " + nextValue);
538 						}
539 
540 						theResourceTypes.put(resName, nextClass);
541 					} catch (ClassNotFoundException e) {
542 						throw new ConfigurationException("Unknown class[" + nextValue + "] for resource definition: " + nextKey.substring("resource.".length()), e);
543 					}
544 				} else {
545 					throw new ConfigurationException("Unexpected property in version property file: " + nextKey + "=" + nextValue);
546 				}
547 			}
548 		} catch (IOException e) {
549 			throw new ConfigurationException("Failed to load model property file from classpath: " + "/ca/uhn/fhir/model/dstu/model.properties");
550 		}
551 
552 		return retVal;
553 	}
554 
555 }