386 lines
16 KiB
Java
Raw Normal View History

2014-02-17 17:58:32 -05:00
package ca.uhn.fhir.context;
2014-02-19 17:33:46 -05:00
import static org.apache.commons.lang3.StringUtils.isBlank;
2014-02-17 17:58:32 -05:00
import java.lang.reflect.Field;
2014-02-19 13:02:51 -05:00
import java.lang.reflect.ParameterizedType;
2014-02-17 17:58:32 -05:00
import java.util.ArrayList;
2014-02-18 08:26:49 -05:00
import java.util.Arrays;
2014-02-17 17:58:32 -05:00
import java.util.HashMap;
import java.util.HashSet;
2014-02-18 18:10:50 -05:00
import java.util.Iterator;
2014-02-19 11:59:12 -05:00
import java.util.LinkedList;
2014-02-17 17:58:32 -05:00
import java.util.List;
import java.util.Map;
import java.util.Set;
2014-02-19 11:59:12 -05:00
import java.util.TreeMap;
2014-02-17 17:58:32 -05:00
import ca.uhn.fhir.model.api.CodeableConceptElement;
import ca.uhn.fhir.model.api.ICodeEnum;
import ca.uhn.fhir.model.api.ICompositeDatatype;
import ca.uhn.fhir.model.api.ICompositeElement;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceReference;
2014-02-18 08:26:49 -05:00
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildResource;
import ca.uhn.fhir.model.api.annotation.Choice;
2014-02-18 18:10:50 -05:00
import ca.uhn.fhir.model.api.annotation.CodeTableDef;
2014-02-19 11:59:12 -05:00
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Narrative;
2014-02-18 18:10:50 -05:00
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.datatype.ICodedDatatype;
2014-02-19 11:59:12 -05:00
import ca.uhn.fhir.model.datatype.NarrativeDt;
2014-02-17 17:58:32 -05:00
class ModelScanner {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
2014-02-18 18:10:50 -05:00
private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>();
private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>();
2014-02-18 08:26:49 -05:00
private Set<Class<? extends IElement>> myScanAlso = new HashSet<Class<? extends IElement>>();
2014-02-19 11:59:12 -05:00
private Set<Class<? extends ICodeEnum>> myScanAlsoCodeTable = new HashSet<Class<? extends ICodeEnum>>();
2014-02-18 18:10:50 -05:00
2014-02-17 17:58:32 -05:00
// private Map<String, RuntimeResourceDefinition>
// myNameToDatatypeDefinitions = new HashMap<String,
// RuntimeDatatypeDefinition>();
2014-02-18 08:26:49 -05:00
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() {
2014-02-18 18:10:50 -05:00
return (myNameToResourceDefinitions);
2014-02-18 08:26:49 -05:00
}
2014-02-17 17:58:32 -05:00
ModelScanner(Class<? extends IResource>... theResourceTypes) throws ConfigurationException {
2014-02-18 18:10:50 -05:00
Set<Class<? extends IElement>> toScan = new HashSet<Class<? extends IElement>>(Arrays.asList(theResourceTypes));
2014-02-19 11:59:12 -05:00
toScan.add(NarrativeDt.class);
2014-02-18 18:10:50 -05:00
do {
for (Class<? extends IElement> nextClass : toScan) {
scan(nextClass);
}
2014-02-19 11:59:12 -05:00
for (Iterator<Class<? extends IElement>> iter = myScanAlso.iterator(); iter.hasNext();) {
2014-02-18 18:10:50 -05:00
if (myClassToElementDefinitions.containsKey(iter.next())) {
iter.remove();
}
}
toScan.clear();
toScan.addAll(myScanAlso);
myScanAlso.clear();
2014-02-19 13:02:51 -05:00
} while (!toScan.isEmpty());
2014-02-19 11:59:12 -05:00
2014-02-18 18:10:50 -05:00
for (BaseRuntimeElementDefinition<?> next : myClassToElementDefinitions.values()) {
next.sealAndInitialize(myClassToElementDefinitions);
2014-02-17 17:58:32 -05:00
}
2014-02-19 11:59:12 -05:00
ourLog.info("Done scanning FHIR library, found {} model entries", myClassToElementDefinitions.size());
2014-02-17 17:58:32 -05:00
}
private String scan(Class<? extends IElement> theClass) throws ConfigurationException {
2014-02-18 18:10:50 -05:00
BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
2014-02-17 17:58:32 -05:00
if (existingDef != null) {
return existingDef.getName();
}
2014-02-18 18:10:50 -05:00
ResourceDef resourceDefinition = theClass.getAnnotation(ResourceDef.class);
2014-02-17 17:58:32 -05:00
if (resourceDefinition != null) {
if (!IResource.class.isAssignableFrom(theClass)) {
2014-02-19 17:33:46 -05:00
throw new ConfigurationException("Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": "
+ theClass.getCanonicalName());
2014-02-17 17:58:32 -05:00
}
@SuppressWarnings("unchecked")
Class<? extends IResource> resClass = (Class<? extends IResource>) theClass;
return scanResource(resClass, resourceDefinition);
}
2014-02-18 18:10:50 -05:00
DatatypeDef datatypeDefinition = theClass.getAnnotation(DatatypeDef.class);
2014-02-17 17:58:32 -05:00
if (datatypeDefinition != null) {
if (ICompositeDatatype.class.isAssignableFrom(theClass)) {
@SuppressWarnings("unchecked")
Class<? extends ICompositeDatatype> resClass = (Class<? extends ICompositeDatatype>) theClass;
return scanCompositeDatatype(resClass, datatypeDefinition);
} else if (IPrimitiveDatatype.class.isAssignableFrom(theClass)) {
2014-02-19 11:59:12 -05:00
@SuppressWarnings({ "unchecked", "rawtypes" })
2014-02-17 17:58:32 -05:00
Class<? extends IPrimitiveDatatype> resClass = (Class<? extends IPrimitiveDatatype>) theClass;
return scanPrimitiveDatatype(resClass, datatypeDefinition);
} else {
2014-02-19 17:33:46 -05:00
throw new ConfigurationException("Resource type contains a @" + DatatypeDef.class.getSimpleName() + " annotation but does not implement " + IDatatype.class.getCanonicalName() + ": "
+ theClass.getCanonicalName());
2014-02-17 17:58:32 -05:00
}
}
2014-02-18 18:10:50 -05:00
CodeTableDef codeTableDefinition = theClass.getAnnotation(CodeTableDef.class);
if (codeTableDefinition != null) {
if (ICodeEnum.class.isAssignableFrom(theClass)) {
@SuppressWarnings("unchecked")
Class<? extends ICodeEnum> resClass = (Class<? extends ICodeEnum>) theClass;
return scanCodeTable(resClass, codeTableDefinition);
} else {
2014-02-19 17:33:46 -05:00
throw new ConfigurationException("Resource type contains a @" + CodeTableDef.class.getSimpleName() + " annotation but does not implement " + ICodeEnum.class.getCanonicalName() + ": "
+ theClass.getCanonicalName());
2014-02-18 18:10:50 -05:00
}
}
2014-02-19 17:33:46 -05:00
throw new ConfigurationException("Resource type does not contain a @" + ResourceDef.class.getSimpleName() + " annotation or a @" + DatatypeDef.class.getSimpleName() + " annotation: "
+ theClass.getCanonicalName());
2014-02-17 17:58:32 -05:00
}
2014-02-18 18:10:50 -05:00
private String scanCompositeDatatype(Class<? extends ICompositeDatatype> theClass, DatatypeDef theDatatypeDefinition) {
2014-02-17 17:58:32 -05:00
ourLog.debug("Scanning resource class: {}", theClass.getName());
String resourceName = theDatatypeDefinition.name();
if (isBlank(resourceName)) {
2014-02-18 18:10:50 -05:00
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
2014-02-17 17:58:32 -05:00
}
RuntimeCompositeDatatypeDefinition resourceDef = new RuntimeCompositeDatatypeDefinition(resourceName, theClass);
2014-02-18 18:10:50 -05:00
myClassToElementDefinitions.put(theClass, resourceDef);
2014-02-17 17:58:32 -05:00
2014-02-19 13:02:51 -05:00
scanCompositeElementForChildren(theClass, resourceDef, null);
2014-02-17 17:58:32 -05:00
return resourceName;
}
2014-02-18 18:10:50 -05:00
private String scanPrimitiveDatatype(Class<? extends IPrimitiveDatatype> theClass, DatatypeDef theDatatypeDefinition) {
2014-02-17 17:58:32 -05:00
ourLog.debug("Scanning resource class: {}", theClass.getName());
String resourceName = theDatatypeDefinition.name();
if (isBlank(resourceName)) {
2014-02-18 18:10:50 -05:00
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
2014-02-17 17:58:32 -05:00
}
RuntimePrimitiveDatatypeDefinition resourceDef = new RuntimePrimitiveDatatypeDefinition(resourceName, theClass);
2014-02-18 18:10:50 -05:00
myClassToElementDefinitions.put(theClass, resourceDef);
2014-02-17 17:58:32 -05:00
return resourceName;
}
2014-02-18 18:10:50 -05:00
private String scanResource(Class<? extends IResource> theClass, ResourceDef resourceDefinition) {
2014-02-17 17:58:32 -05:00
ourLog.debug("Scanning resource class: {}", theClass.getName());
String resourceName = resourceDefinition.name();
if (isBlank(resourceName)) {
2014-02-18 18:10:50 -05:00
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
2014-02-17 17:58:32 -05:00
}
2014-02-18 18:10:50 -05:00
if (myNameToResourceDefinitions.containsKey(resourceName)) {
if (!myNameToResourceDefinitions.get(resourceName).getImplementingClass().equals(theClass)) {
2014-02-19 17:33:46 -05:00
throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '"
+ myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'");
2014-02-17 17:58:32 -05:00
}
return resourceName;
}
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(theClass, resourceName);
2014-02-18 18:10:50 -05:00
myClassToElementDefinitions.put(theClass, resourceDef);
myNameToResourceDefinitions.put(resourceName, resourceDef);
2014-02-17 17:58:32 -05:00
2014-02-19 13:02:51 -05:00
scanCompositeElementForChildren(theClass, resourceDef, resourceDefinition.identifierOrder());
2014-02-17 17:58:32 -05:00
return resourceName;
}
2014-02-19 11:59:12 -05:00
@SuppressWarnings("unchecked")
2014-02-19 13:02:51 -05:00
private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition, Integer theIdentifierOrder) {
2014-02-17 17:58:32 -05:00
Set<String> elementNames = new HashSet<String>();
2014-02-19 11:59:12 -05:00
TreeMap<Integer, BaseRuntimeChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeChildDefinition>();
LinkedList<Class<? extends ICompositeElement>> classes = new LinkedList<Class<? extends ICompositeElement>>();
Class<? extends ICompositeElement> current = theClass;
do {
classes.push(current);
if (ICompositeElement.class.isAssignableFrom(current.getSuperclass())) {
current = (Class<? extends ICompositeElement>) current.getSuperclass();
2014-02-19 17:33:46 -05:00
} else {
2014-02-19 11:59:12 -05:00
current = null;
}
2014-02-19 17:33:46 -05:00
} while (current != null);
2014-02-19 11:59:12 -05:00
for (Class<? extends ICompositeElement> next : classes) {
scanCompositeElementForChildren(next, theDefinition, elementNames, orderToElementDef);
}
2014-02-19 13:02:51 -05:00
while (orderToElementDef.firstKey() < 0) {
BaseRuntimeChildDefinition elementDef = orderToElementDef.remove(orderToElementDef.firstKey());
if (elementDef.getElementName().equals("identifier")) {
orderToElementDef.put(theIdentifierOrder, elementDef);
2014-02-19 17:33:46 -05:00
} else {
2014-02-19 13:02:51 -05:00
throw new ConfigurationException("Don't know how to handle element: " + elementDef.getElementName());
}
}
2014-02-19 17:33:46 -05:00
2014-02-19 11:59:12 -05:00
for (int i = 0; i < orderToElementDef.size(); i++) {
if (!orderToElementDef.containsKey(i)) {
2014-02-19 17:33:46 -05:00
throw new ConfigurationException("Type '" + theClass.getCanonicalName() + "' does not have a child with order " + i
+ " (in other words, there are gaps between specified child orders)");
2014-02-19 11:59:12 -05:00
}
BaseRuntimeChildDefinition next = orderToElementDef.get(i);
theDefinition.addChild(next);
}
}
2014-02-19 13:02:51 -05:00
@SuppressWarnings("unchecked")
2014-02-19 17:33:46 -05:00
private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition, Set<String> elementNames,
TreeMap<Integer, BaseRuntimeChildDefinition> orderToElementDef) {
2014-02-19 11:59:12 -05:00
for (Field next : theClass.getDeclaredFields()) {
2014-02-19 17:33:46 -05:00
2014-02-19 11:59:12 -05:00
Narrative hasNarrative = next.getAnnotation(Narrative.class);
if (hasNarrative != null) {
RuntimeChildNarrativeDefinition def;
try {
def = new RuntimeChildNarrativeDefinition(next, hasNarrative.name());
} catch (Exception e) {
throw new ConfigurationException("Failed to find narrative field", e);
}
theDefinition.addChild(def);
2014-02-19 17:33:46 -05:00
continue;
2014-02-19 11:59:12 -05:00
}
2014-02-18 08:26:49 -05:00
Child element = next.getAnnotation(Child.class);
2014-02-17 17:58:32 -05:00
if (element == null) {
2014-02-19 17:33:46 -05:00
ourLog.debug("Ignoring non-type field '" + next.getName() + "' on target type: " + theClass);
2014-02-17 17:58:32 -05:00
continue;
}
String elementName = element.name();
int order = element.order();
int min = element.min();
int max = element.max();
2014-02-19 11:59:12 -05:00
while (order == Child.ORDER_UNKNOWN && orderToElementDef.containsKey(order)) {
order--;
}
2014-02-19 17:33:46 -05:00
2014-02-18 08:26:49 -05:00
Choice choiceAttr = element.choice();
2014-02-17 17:58:32 -05:00
List<Class<? extends IElement>> choiceTypes = new ArrayList<Class<? extends IElement>>();
for (Class<? extends IElement> nextChoiceType : choiceAttr.types()) {
choiceTypes.add(nextChoiceType);
}
if (orderToElementDef.containsKey(order)) {
throw new ConfigurationException("Detected duplicate field order '" + order + "' in type '" + theClass.getCanonicalName() + "'");
}
if (elementNames.contains(elementName)) {
throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '" + theClass.getCanonicalName() + "'");
}
2014-02-18 08:26:49 -05:00
ChildResource resRefAnnotation = next.getAnnotation(ChildResource.class);
2014-02-17 17:58:32 -05:00
if (choiceTypes.isEmpty() == false) {
2014-02-18 08:26:49 -05:00
/*
* Child is a choice element
*/
myScanAlso.addAll(choiceTypes);
RuntimeChildChoiceDefinition def = new RuntimeChildChoiceDefinition(next, elementName, min, max, choiceTypes);
2014-02-17 17:58:32 -05:00
orderToElementDef.put(order, def);
} else if (ResourceReference.class.isAssignableFrom(next.getType())) {
2014-02-18 08:26:49 -05:00
/*
* Child is a resource reference
*/
2014-02-17 17:58:32 -05:00
if (resRefAnnotation == null) {
2014-02-19 17:33:46 -05:00
throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is a resource reference but does not have a @"
+ ChildResource.class.getSimpleName() + " annotation");
2014-02-17 17:58:32 -05:00
}
2014-02-18 08:26:49 -05:00
Class<? extends IResource>[] refType = resRefAnnotation.types();
List<Class<? extends IResource>> refTypesList = Arrays.asList(refType);
myScanAlso.addAll(refTypesList);
RuntimeChildResourceDefinition def = new RuntimeChildResourceDefinition(next, elementName, min, max, refTypesList);
2014-02-17 17:58:32 -05:00
orderToElementDef.put(order, def);
2014-02-18 18:10:50 -05:00
2014-02-17 17:58:32 -05:00
} else {
if (resRefAnnotation != null) {
2014-02-19 17:33:46 -05:00
throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a resource reference but has a @"
+ ChildResource.class.getSimpleName() + " annotation");
2014-02-17 17:58:32 -05:00
}
2014-02-19 13:02:51 -05:00
Class<? extends IDatatype> nextDatatype;
if (IDatatype.class.isAssignableFrom(next.getType())) {
nextDatatype = (Class<? extends IDatatype>) next.getType();
} else {
2014-02-19 17:33:46 -05:00
if (List.class.isAssignableFrom(next.getType())) {
Class<?> type = getGenericCollectionTypeOfField(next);
2014-02-19 13:02:51 -05:00
if (IDatatype.class.isAssignableFrom(type)) {
2014-02-19 17:33:46 -05:00
nextDatatype = (Class<? extends IDatatype>) type;
} else {
throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a resource reference and is not an instance of type "
+ IDatatype.class.getName());
2014-02-19 13:02:51 -05:00
}
2014-02-19 17:33:46 -05:00
} else {
/* TODO: detect when someone has used a different collection (set, etc.) and
* give a nice error that indicates that they need to use List..
*/
throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a resource reference and is not an instance of type "
+ IDatatype.class.getName());
2014-02-19 13:02:51 -05:00
}
2014-02-17 17:58:32 -05:00
}
2014-02-18 18:10:50 -05:00
myScanAlso.add(nextDatatype);
2014-02-18 08:26:49 -05:00
2014-02-18 18:10:50 -05:00
BaseRuntimeChildDatatypeDefinition def;
2014-02-18 08:26:49 -05:00
if (IPrimitiveDatatype.class.isAssignableFrom(next.getType())) {
2014-02-18 18:10:50 -05:00
def = new RuntimeChildPrimitiveDatatypeDefinition(next, elementName, min, max, nextDatatype);
} else {
def = new RuntimeChildCompositeDatatypeDefinition(next, elementName, min, max, nextDatatype);
}
CodeableConceptElement concept = next.getAnnotation(CodeableConceptElement.class);
if (concept != null) {
if (!ICodedDatatype.class.isAssignableFrom(nextDatatype)) {
2014-02-19 17:33:46 -05:00
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());
2014-02-18 18:10:50 -05:00
} else {
Class<? extends ICodeEnum> type = concept.type();
2014-02-19 11:59:12 -05:00
myScanAlsoCodeTable.add(type);
2014-02-18 18:10:50 -05:00
def.setCodeType(type);
}
2014-02-17 17:58:32 -05:00
}
2014-02-18 08:26:49 -05:00
2014-02-18 18:10:50 -05:00
orderToElementDef.put(order, def);
// TODO: handle codes
// if (concept != null) {
// Class<? extends ICodeEnum> codeType = concept.type();
// String codeTableName = scanCodeTable(codeType);
// @SuppressWarnings("unchecked")
2014-02-19 11:59:12 -05:00
// String datatypeName = scan((Class<? extends IDatatype>)
// next.getType());
// RuntimeChildCompositeDatatypeDefinition def = new
// RuntimeChildCompositeDatatypeDefinition(next, elementName,
// datatypeName, min, max, codeTableName);
2014-02-18 18:10:50 -05:00
// orderToElementDef.put(order, def);
// } else {
// @SuppressWarnings("unchecked")
// orderToElementDef.put(order, def);
// }
2014-02-17 17:58:32 -05:00
}
elementNames.add(elementName);
}
}
2014-02-19 17:33:46 -05:00
private static Class<?> getGenericCollectionTypeOfField(Field next) {
// Type genericSuperclass = next.getType().getGenericSuperclass();
Class<?> type;
// if (genericSuperclass == null) {
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
type = (Class<?>) collectionType.getActualTypeArguments()[0];
// }else {
// Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
// type = (Class<?>) actualTypeArguments[0];
// }
return type;
}
2014-02-18 18:10:50 -05:00
private String scanCodeTable(Class<? extends ICodeEnum> theCodeType, CodeTableDef theCodeTableDefinition) {
return null; // TODO: implement
2014-02-17 17:58:32 -05:00
}
}