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