263 lines
12 KiB
Java
Raw Normal View History

2014-02-17 17:58:32 -05:00
package ca.uhn.fhir.context;
import static org.apache.commons.lang3.StringUtils.*;
import java.lang.reflect.Field;
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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.Datatype;
import ca.uhn.fhir.model.api.annotation.Choice;
2014-02-17 17:58:32 -05:00
import ca.uhn.fhir.model.resource.ResourceDefinition;
class ModelScanner {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToResourceDefinitions = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>();
2014-02-18 08:26:49 -05:00
private Map<String, RuntimeResourceDefinition> myNameToElementDefinitions = new HashMap<String, RuntimeResourceDefinition>();
private Set<Class<? extends IElement>> myScanAlso = new HashSet<Class<? extends IElement>>();
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() {
return (myNameToElementDefinitions);
}
2014-02-17 17:58:32 -05:00
ModelScanner(Class<? extends IResource>... theResourceTypes) throws ConfigurationException {
for (Class<? extends IResource> nextClass : theResourceTypes) {
scan(nextClass);
}
}
private String scan(Class<? extends IElement> theClass) throws ConfigurationException {
BaseRuntimeElementDefinition<?> existingDef = myClassToResourceDefinitions.get(theClass);
if (existingDef != null) {
return existingDef.getName();
}
ResourceDefinition resourceDefinition = theClass.getAnnotation(ResourceDefinition.class);
if (resourceDefinition != null) {
if (!IResource.class.isAssignableFrom(theClass)) {
throw new ConfigurationException("Resource type contains a @" + ResourceDefinition.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName());
}
@SuppressWarnings("unchecked")
Class<? extends IResource> resClass = (Class<? extends IResource>) theClass;
return scanResource(resClass, resourceDefinition);
}
2014-02-18 08:26:49 -05:00
Datatype datatypeDefinition = theClass.getAnnotation(Datatype.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)) {
@SuppressWarnings("unchecked")
Class<? extends IPrimitiveDatatype> resClass = (Class<? extends IPrimitiveDatatype>) theClass;
return scanPrimitiveDatatype(resClass, datatypeDefinition);
} else {
2014-02-18 08:26:49 -05:00
throw new ConfigurationException("Resource type contains a @" + Datatype.class.getSimpleName() + " annotation but does not implement " + IDatatype.class.getCanonicalName() + ": " + theClass.getCanonicalName());
2014-02-17 17:58:32 -05:00
}
}
2014-02-18 08:26:49 -05:00
throw new ConfigurationException("Resource type does not contain a @" + ResourceDefinition.class.getSimpleName() + " annotation or a @" + Datatype.class.getSimpleName() + " annotation: " + theClass.getCanonicalName());
2014-02-17 17:58:32 -05:00
}
2014-02-18 08:26:49 -05:00
private String scanCompositeDatatype(Class<? extends ICompositeDatatype> theClass, Datatype theDatatypeDefinition) {
2014-02-17 17:58:32 -05:00
ourLog.debug("Scanning resource class: {}", theClass.getName());
String resourceName = theDatatypeDefinition.name();
if (isBlank(resourceName)) {
throw new ConfigurationException("Resource type @" + ResourceDefinition.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
}
2014-02-18 08:26:49 -05:00
if (myNameToElementDefinitions.containsKey(resourceName)) {
if (!myNameToElementDefinitions.get(resourceName).getImplementingClass().equals(theClass)) {
throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" + myNameToElementDefinitions.get(resourceName).getImplementingClass() + "'");
2014-02-17 17:58:32 -05:00
}
return resourceName;
}
RuntimeCompositeDatatypeDefinition resourceDef = new RuntimeCompositeDatatypeDefinition(resourceName, theClass);
myClassToResourceDefinitions.put(theClass, resourceDef);
2014-02-18 08:26:49 -05:00
myNameToElementDefinitions.put(resourceName, resourceDef);
2014-02-17 17:58:32 -05:00
scanCompositeElementForChildren(theClass, resourceDef);
return resourceName;
}
2014-02-18 08:26:49 -05:00
private String scanPrimitiveDatatype(Class<? extends IPrimitiveDatatype> theClass, Datatype theDatatypeDefinition) {
2014-02-17 17:58:32 -05:00
ourLog.debug("Scanning resource class: {}", theClass.getName());
String resourceName = theDatatypeDefinition.name();
if (isBlank(resourceName)) {
throw new ConfigurationException("Resource type @" + ResourceDefinition.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
}
2014-02-18 08:26:49 -05:00
if (myNameToElementDefinitions.containsKey(resourceName)) {
if (!myNameToElementDefinitions.get(resourceName).getImplementingClass().equals(theClass)) {
throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" + myNameToElementDefinitions.get(resourceName).getImplementingClass() + "'");
2014-02-17 17:58:32 -05:00
}
return resourceName;
}
RuntimePrimitiveDatatypeDefinition resourceDef = new RuntimePrimitiveDatatypeDefinition(resourceName, theClass);
myClassToResourceDefinitions.put(theClass, resourceDef);
2014-02-18 08:26:49 -05:00
myNameToElementDefinitions.put(resourceName, resourceDef);
2014-02-17 17:58:32 -05:00
return resourceName;
}
private String scanResource(Class<? extends IResource> theClass, ResourceDefinition resourceDefinition) {
ourLog.debug("Scanning resource class: {}", theClass.getName());
String resourceName = resourceDefinition.name();
if (isBlank(resourceName)) {
throw new ConfigurationException("Resource type @" + ResourceDefinition.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
}
2014-02-18 08:26:49 -05:00
if (myNameToElementDefinitions.containsKey(resourceName)) {
if (!myNameToElementDefinitions.get(resourceName).getImplementingClass().equals(theClass)) {
throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" + myNameToElementDefinitions.get(resourceName).getImplementingClass() + "'");
2014-02-17 17:58:32 -05:00
}
return resourceName;
}
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(theClass, resourceName);
myClassToResourceDefinitions.put(theClass, resourceDef);
2014-02-18 08:26:49 -05:00
myNameToElementDefinitions.put(resourceName, resourceDef);
2014-02-17 17:58:32 -05:00
scanCompositeElementForChildren(theClass, resourceDef);
return resourceName;
}
2014-02-18 08:26:49 -05:00
private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition) {
2014-02-17 17:58:32 -05:00
Set<String> elementNames = new HashSet<String>();
Map<Integer, BaseRuntimeChildDefinition> orderToElementDef = new HashMap<Integer, BaseRuntimeChildDefinition>();
for (Field next : theClass.getFields()) {
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) {
ourLog.debug("Ignoring non-type field: " + next.getName());
continue;
}
String elementName = element.name();
int order = element.order();
int min = element.min();
int max = element.max();
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-18 08:26:49 -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 08:26:49 -05:00
2014-02-17 17:58:32 -05:00
} else {
if (resRefAnnotation != null) {
2014-02-18 08:26:49 -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
}
if (!IDatatype.class.isAssignableFrom(next.getType())) {
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-18 08:26:49 -05:00
@SuppressWarnings("unchecked")
Class<? extends IDatatype> nextDatatype = (Class<? extends IDatatype>) next.getType();
2014-02-17 17:58:32 -05:00
CodeableConceptElement concept = next.getAnnotation(CodeableConceptElement.class);
2014-02-18 08:26:49 -05:00
if (IPrimitiveDatatype.class.isAssignableFrom(next.getType())) {
myScanAlso.add(nextDatatype);
RuntimeChildPrimitiveDatatypeDefinition def = new RuntimeChildPrimitiveDatatypeDefinition(next, elementName, datatypeName, min, max);
}else {
2014-02-17 17:58:32 -05:00
@SuppressWarnings("unchecked")
2014-02-18 08:26:49 -05:00
String datatypeName = scan(nextDatatype);
RuntimeChildCompositeDatatypeDefinition def = new RuntimeChildCompositeDatatypeDefinition(next, elementName, datatypeName, min, max, datatypeName);
2014-02-17 17:58:32 -05:00
orderToElementDef.put(order, def);
}
2014-02-18 08:26:49 -05:00
// TODO: handle codes
// if (concept != null) {
// Class<? extends ICodeEnum> codeType = concept.type();
// String codeTableName = scanCodeTable(codeType);
// @SuppressWarnings("unchecked")
// String datatypeName = scan((Class<? extends IDatatype>) next.getType());
// RuntimeChildCompositeDatatypeDefinition def = new RuntimeChildCompositeDatatypeDefinition(next, elementName, datatypeName, min, max, codeTableName);
// orderToElementDef.put(order, def);
// } else {
// @SuppressWarnings("unchecked")
// orderToElementDef.put(order, def);
// }
2014-02-17 17:58:32 -05:00
}
elementNames.add(elementName);
}
for (int i = 0; i < orderToElementDef.size(); i++) {
if (!orderToElementDef.containsKey(i)) {
throw new ConfigurationException("Type '" + theClass.getCanonicalName() + "' does not have a child with order " + i + " (in other words, there are gaps between specified child orders)");
}
BaseRuntimeChildDefinition next = orderToElementDef.get(i);
theDefinition.addChild(next);
}
}
private String scanCodeTable(Class<? extends ICodeEnum> theCodeType) {
// TODO Auto-generated method stub
return null;
}
}