Significant performance enhancements for context startup

This commit is contained in:
James Agnew 2016-06-06 11:15:11 -07:00
parent 99568a4b30
commit 7e57aed5d6
43 changed files with 1349 additions and 625 deletions

View File

@ -6,6 +6,7 @@ import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.PerformanceOptionsEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
@ -25,12 +26,38 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.method.SearchStyleEnum;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
public class GenericClientExample {
public static void deferModelScanning() {
// START SNIPPET: deferModelScanning
// Create a context and configure it for deferred child scanning
FhirContext ctx = FhirContext.forDstu2();
ctx.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING);
// Now create a client and use it
String serverBase = "http://fhirtest.uhn.ca/baseDstu2";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// END SNIPPET: deferModelScanning
}
public static void performance() {
// START SNIPPET: dontValidate
// Create a context
FhirContext ctx = FhirContext.forDstu2();
// Disable server validation (don't pull the server's metadata first)
ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
// Now create a client and use it
String serverBase = "http://fhirtest.uhn.ca/baseDstu2";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// END SNIPPET: dontValidate
}
public static void simpleExample() {
// START SNIPPET: simple

View File

@ -97,6 +97,9 @@ public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDecl
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
myElementDefinition = theClassToElementDefinitions.get(getDatatype());
if (myElementDefinition == null) {
myElementDefinition = theContext.getElementDefinition(getDatatype());
}
assert myElementDefinition != null : "Unknown type: " + getDatatype();
}

View File

@ -1,5 +1,8 @@
package ca.uhn.fhir.context;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/*
* #%L
* HAPI FHIR - Core Library
@ -23,23 +26,64 @@ package ca.uhn.fhir.context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IBoundCodeableConcept;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.IResourceBlock;
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.ReflectionUtil;
public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> extends BaseRuntimeElementDefinition<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeElementCompositeDefinition.class);
private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<BaseRuntimeChildDefinition>();
private List<BaseRuntimeChildDefinition> myChildrenAndExtensions;
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions;
private FhirContext myContext;
private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
public BaseRuntimeElementCompositeDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
private volatile boolean mySealed;
public BaseRuntimeElementCompositeDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theName, theImplementingClass, theStandardType);
myContext = theContext;
myClassToElementDefinitions = theClassToElementDefinitions;
}
void addChild(BaseRuntimeChildDefinition theNext) {
@ -53,11 +97,13 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
}
public BaseRuntimeChildDefinition getChildByName(String theName){
validateSealed();
BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
return retVal;
}
public BaseRuntimeChildDefinition getChildByNameOrThrowDataFormatException(String theName) throws DataFormatException {
validateSealed();
BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
if (retVal == null) {
throw new DataFormatException("Unknown child name '" + theName + "' in element " + getName() + " - Valid names are: " + new TreeSet<String>(myNameToChild.keySet()));
@ -66,15 +112,354 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
}
public List<BaseRuntimeChildDefinition> getChildren() {
validateSealed();
return myChildren;
}
public List<BaseRuntimeChildDefinition> getChildrenAndExtension() {
validateSealed();
return myChildrenAndExtensions;
}
/**
* Has this class been sealed
*/
public boolean isSealed() {
return mySealed;
}
@SuppressWarnings("unchecked")
private void scanCompositeElementForChildren(Class<? extends IBase> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition) {
Set<String> elementNames = new HashSet<String>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
LinkedList<Class<? extends IBase>> classes = new LinkedList<Class<? extends IBase>>();
/*
* We scan classes for annotated fields in the class but also all of its superclasses
*/
Class<? extends IBase> current = theClass;
Map<String, Integer> forcedOrder = null;
do {
if (forcedOrder == null) {
ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
if (childOrder != null) {
forcedOrder = new HashMap<String, Integer>();
for (int i = 0; i < childOrder.names().length; i++) {
forcedOrder.put(childOrder.names()[i], i);
}
}
}
classes.push(current);
if (IBase.class.isAssignableFrom(current.getSuperclass())) {
current = (Class<? extends IBase>) current.getSuperclass();
} else {
current = null;
}
} while (current != null);
for (Class<? extends IBase> next : classes) {
scanCompositeElementForChildren(next, elementNames, orderToElementDef, orderToExtensionDef);
}
if (forcedOrder != null) {
/*
* Find out how many elements don't match any entry in the list
* for forced order. Those elements come first.
*/
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
int unknownCount = 0;
for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
if (!forcedOrder.containsKey(nextEntry.getElementName())) {
newOrderToExtensionDef.put(unknownCount, nextEntry);
unknownCount++;
}
}
for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
if (forcedOrder.containsKey(nextEntry.getElementName())) {
Integer newOrder = forcedOrder.get(nextEntry.getElementName());
newOrderToExtensionDef.put(newOrder + unknownCount, nextEntry);
}
}
orderToElementDef = newOrderToExtensionDef;
}
// while (orderToElementDef.size() > 0 && orderToElementDef.firstKey() <
// 0) {
// BaseRuntimeDeclaredChildDefinition elementDef =
// orderToElementDef.remove(orderToElementDef.firstKey());
// if (elementDef.getElementName().equals("identifier")) {
// orderToElementDef.put(theIdentifierOrder, elementDef);
// } else {
// throw new ConfigurationException("Don't know how to handle element: "
// + elementDef.getElementName());
// }
// }
TreeSet<Integer> orders = new TreeSet<Integer>();
orders.addAll(orderToElementDef.keySet());
orders.addAll(orderToExtensionDef.keySet());
for (Integer i : orders) {
BaseRuntimeChildDefinition nextChild = orderToElementDef.get(i);
if (nextChild != null) {
theDefinition.addChild(nextChild);
}
BaseRuntimeDeclaredChildDefinition nextExt = orderToExtensionDef.get(i);
if (nextExt != null) {
theDefinition.addExtension((RuntimeChildDeclaredExtensionDefinition) nextExt);
}
}
}
@SuppressWarnings("unchecked")
private void scanCompositeElementForChildren(Class<? extends IBase> theClass, Set<String> elementNames, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef,
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
int baseElementOrder = theOrderToElementDef.isEmpty() ? 0 : theOrderToElementDef.lastEntry().getKey() + 1;
for (Field next : theClass.getDeclaredFields()) {
if (Modifier.isFinal(next.getModifiers())) {
ourLog.trace("Ignoring constant {} on target type {}", next.getName(), theClass);
continue;
}
Child childAnnotation = ModelScanner.pullAnnotation(next, Child.class);
if (childAnnotation == null) {
ourLog.trace("Ignoring non @Child field {} on target type {}", next.getName(), theClass);
continue;
}
Description descriptionAnnotation = ModelScanner.pullAnnotation(next, Description.class);
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
Extension extensionAttr = ModelScanner.pullAnnotation(next, Extension.class);
if (extensionAttr != null) {
orderMap = theOrderToExtensionDef;
}
String elementName = childAnnotation.name();
int order = childAnnotation.order();
boolean childIsChoiceType = false;
if (order == Child.REPLACE_PARENT) {
if (extensionAttr != null) {
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
if (nextDef instanceof RuntimeChildDeclaredExtensionDefinition) {
if (nextDef.getExtensionUrl().equals(extensionAttr.url())) {
order = nextEntry.getKey();
orderMap.remove(nextEntry.getKey());
elementNames.remove(elementName);
break;
}
}
}
if (order == Child.REPLACE_PARENT) {
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() + " could be found on type " + next.getDeclaringClass().getSimpleName());
}
} else {
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
if (elementName.equals(nextDef.getElementName())) {
order = nextEntry.getKey();
BaseRuntimeDeclaredChildDefinition existing = orderMap.remove(nextEntry.getKey());
elementNames.remove(elementName);
/*
* See #350 - If the original field (in the superclass) with the given name is a choice, then we need to make sure
* that the field which replaces is a choice even if it's only a choice of one type - this is because the
* element name when serialized still needs to reflect the datatype
*/
if (existing instanceof RuntimeChildChoiceDefinition) {
childIsChoiceType = true;
}
break;
}
}
if (order == Child.REPLACE_PARENT) {
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 " + next.getDeclaringClass().getSimpleName());
}
}
}
if (order < 0 && order != Child.ORDER_UNKNOWN) {
throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + next.getName() + "' on target type: " + theClass);
}
if (order != Child.ORDER_UNKNOWN) {
order = order + baseElementOrder;
}
// int min = childAnnotation.min();
// int max = childAnnotation.max();
/*
* Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict with any given IDs and can be figured out later
*/
if (order == Child.ORDER_UNKNOWN) {
order = Integer.valueOf(0);
while (orderMap.containsKey(order)) {
order++;
}
}
List<Class<? extends IBase>> choiceTypes = new ArrayList<Class<? extends IBase>>();
for (Class<? extends IBase> nextChoiceType : childAnnotation.type()) {
choiceTypes.add(nextChoiceType);
}
if (orderMap.containsKey(order)) {
throw new ConfigurationException("Detected duplicate field order '" + childAnnotation.order() + "' for element named '" + elementName + "' in type '" + theClass.getCanonicalName() + "' - Already had: " + orderMap.get(order).getElementName());
}
if (elementNames.contains(elementName)) {
throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '" + theClass.getCanonicalName() + "'");
}
Class<?> nextElementType = ModelScanner.determineElementType(next);
if (childAnnotation.name().equals("extension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
RuntimeChildExtension def = new RuntimeChildExtension(next, childAnnotation.name(), childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (childAnnotation.name().equals("modifierExtension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
RuntimeChildExtension def = new RuntimeChildExtension(next, childAnnotation.name(), childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (BaseContainedDt.class.isAssignableFrom(nextElementType) || (childAnnotation.name().equals("contained") && IBaseResource.class.isAssignableFrom(nextElementType))) {
/*
* Child is contained resources
*/
RuntimeChildContainedResources def = new RuntimeChildContainedResources(next, childAnnotation, descriptionAnnotation, elementName);
orderMap.put(order, def);
} else if (IAnyResource.class.isAssignableFrom(nextElementType) || IResource.class.equals(nextElementType)) {
/*
* Child is a resource as a direct child, as in Bundle.entry.resource
*/
RuntimeChildDirectResource def = new RuntimeChildDirectResource(next, childAnnotation, descriptionAnnotation, elementName);
orderMap.put(order, def);
} else {
childIsChoiceType |= choiceTypes.size() > 1;
if (childIsChoiceType && !BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) && !IBaseReference.class.isAssignableFrom(nextElementType)) {
RuntimeChildChoiceDefinition def = new RuntimeChildChoiceDefinition(next, elementName, childAnnotation, descriptionAnnotation, choiceTypes);
orderMap.put(order, def);
} else if (next.getType().equals(ExtensionDt.class)) {
RuntimeChildExtensionDt def = new RuntimeChildExtensionDt(next, elementName, childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (extensionAttr != null) {
/*
* Child is an extension
*/
Class<? extends IBase> et = (Class<? extends IBase>) nextElementType;
Object binder = null;
if (BoundCodeDt.class.isAssignableFrom(nextElementType) || IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
binder = ModelScanner.getBoundCodeBinder(next);
}
RuntimeChildDeclaredExtensionDefinition def = new RuntimeChildDeclaredExtensionDefinition(next, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et,
binder);
if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
def.setEnumerationType(ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next));
}
orderMap.put(order, def);
} else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) || IBaseReference.class.isAssignableFrom(nextElementType)) {
/*
* Child is a resource reference
*/
List<Class<? extends IBaseResource>> refTypesList = new ArrayList<Class<? extends IBaseResource>>();
for (Class<? extends IElement> nextType : childAnnotation.type()) {
if (IBaseReference.class.isAssignableFrom(nextType)) {
refTypesList.add(myContext.getVersion().getVersion().isRi() ? IAnyResource.class : IResource.class);
continue;
} else if (IBaseResource.class.isAssignableFrom(nextType) == false) {
throw new ConfigurationException("Field '" + next.getName() + "' in class '" + next.getDeclaringClass().getCanonicalName() + "' is of type " + BaseResourceReferenceDt.class + " but contains a non-resource type: " + nextType.getCanonicalName());
}
refTypesList.add((Class<? extends IBaseResource>) nextType);
}
RuntimeChildResourceDefinition def = new RuntimeChildResourceDefinition(next, elementName, childAnnotation, descriptionAnnotation, refTypesList);
orderMap.put(order, def);
} else if (IResourceBlock.class.isAssignableFrom(nextElementType) || IBaseBackboneElement.class.isAssignableFrom(nextElementType)
|| IBaseDatatypeElement.class.isAssignableFrom(nextElementType)) {
/*
* Child is a resource block (i.e. a sub-tag within a resource) TODO: do these have a better name according to HL7?
*/
Class<? extends IBase> blockDef = (Class<? extends IBase>) nextElementType;
RuntimeChildResourceBlockDefinition def = new RuntimeChildResourceBlockDefinition(myContext, next, childAnnotation, descriptionAnnotation, elementName, blockDef);
orderMap.put(order, def);
} else if (IDatatype.class.equals(nextElementType) || IElement.class.equals(nextElementType) || "Type".equals(nextElementType.getSimpleName())
|| IBaseDatatype.class.equals(nextElementType)) {
RuntimeChildAny def = new RuntimeChildAny(next, elementName, childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (IDatatype.class.isAssignableFrom(nextElementType) || IPrimitiveType.class.isAssignableFrom(nextElementType) || ICompositeType.class.isAssignableFrom(nextElementType)
|| IBaseDatatype.class.isAssignableFrom(nextElementType) || IBaseExtension.class.isAssignableFrom(nextElementType)) {
Class<? extends IBase> nextDatatype = (Class<? extends IBase>) nextElementType;
BaseRuntimeChildDatatypeDefinition def;
if (IPrimitiveType.class.isAssignableFrom(nextElementType)) {
if (nextElementType.equals(BoundCodeDt.class)) {
IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(next);
Class<? extends Enum<?>> enumType = ModelScanner.determineEnumTypeForBoundField(next);
def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
} else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
Class<? extends Enum<?>> binderType = ModelScanner.determineEnumTypeForBoundField(next);
def = new RuntimeChildPrimitiveEnumerationDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binderType);
} else {
def = new RuntimeChildPrimitiveDatatypeDefinition(next, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
}
} else if (IBaseXhtml.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildXhtmlDatatypeDefinition(next, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
} else {
if (IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(next);
Class<? extends Enum<?>> enumType = ModelScanner.determineEnumTypeForBoundField(next);
def = new RuntimeChildCompositeBoundDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
} else if (BaseNarrativeDt.class.isAssignableFrom(nextElementType) || INarrative.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildNarrativeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
} else {
def = new RuntimeChildCompositeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
}
}
orderMap.put(order, def);
} else {
throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a valid child type: " + nextElementType);
}
}
elementNames.add(elementName);
}
}
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
if (mySealed) {
return;
}
mySealed = true;
scanCompositeElementForChildren(getImplementingClass(), this);
super.sealAndInitialize(theContext, theClassToElementDefinitions);
for (BaseRuntimeChildDefinition next : myChildren) {
@ -141,6 +526,16 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
myChildrenAndExtensions=Collections.unmodifiableList(children);
}
@Override
protected void validateSealed() {
if (!mySealed) {
synchronized(myContext) {
sealAndInitialize(myContext, myClassToElementDefinitions);
}
}
}
private static int findIndex(List<BaseRuntimeChildDefinition> theChildren, String theName, boolean theDefaultAtEnd) {
int index = theDefaultAtEnd ? theChildren.size() : -1;
for (ListIterator<BaseRuntimeChildDefinition> iter = theChildren.listIterator(); iter.hasNext(); ) {
@ -152,4 +547,5 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
return index;
}
}

View File

@ -21,7 +21,6 @@ package ca.uhn.fhir.context;
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -35,14 +34,14 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
private static final Class<Void> VOID_CLASS = Void.class;
private final String myName;
private final Class<? extends T> myImplementingClass;
private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>());
private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private final Class<? extends T> myImplementingClass;
private final String myName;
private final boolean myStandardType;
private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>());
private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
public BaseRuntimeElementDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
assert StringUtils.isNotBlank(theName);
@ -60,15 +59,6 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
myImplementingClass = theImplementingClass;
}
public boolean isStandardType() {
return myStandardType;
}
@Override
public String toString() {
return getClass().getSimpleName()+"[" + getName() + ", " + getImplementingClass().getSimpleName() + "]";
}
public void addExtension(RuntimeChildDeclaredExtensionDefinition theExtension) {
if (theExtension == null) {
throw new NullPointerException();
@ -76,56 +66,7 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
myExtensions.add(theExtension);
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
return myExtensions;
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
return myExtensionsModifier;
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
return myExtensionsNonModifier;
}
/**
* @return Returns null if none
*/
public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(String theExtensionUrl) {
return myUrlToExtension.get(theExtensionUrl);
}
/**
* @return Returns the runtime name for this resource (i.e. the name that
* will be used in encoded messages)
*/
public String getName() {
return myName;
}
public T newInstance() {
return newInstance(null);
}
public T newInstance(Object theArgument) {
try {
if (theArgument == null) {
return getConstructor(null).newInstance(null);
} else {
return getConstructor(theArgument).newInstance(theArgument);
}
} catch (InstantiationException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
} catch (InvocationTargetException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
} catch (SecurityException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
}
}
public abstract ChildTypeEnum getChildType();
@SuppressWarnings("unchecked")
private Constructor<T> getConstructor(Object theArgument) {
@ -160,10 +101,61 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
return retVal;
}
/**
* @return Returns null if none
*/
public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(String theExtensionUrl) {
validateSealed();
return myUrlToExtension.get(theExtensionUrl);
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
validateSealed();
return myExtensions;
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
validateSealed();
return myExtensionsModifier;
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
validateSealed();
return myExtensionsNonModifier;
}
public Class<? extends T> getImplementingClass() {
return myImplementingClass;
}
/**
* @return Returns the runtime name for this resource (i.e. the name that
* will be used in encoded messages)
*/
public String getName() {
return myName;
}
public boolean isStandardType() {
return myStandardType;
}
public T newInstance() {
return newInstance(null);
}
public T newInstance(Object theArgument) {
try {
if (theArgument == null) {
return getConstructor(null).newInstance(null);
} else {
return getConstructor(theArgument).newInstance(theArgument);
}
} catch (Exception e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
}
}
/**
* Invoked prior to use to perform any initialization and make object
* mutable.
@ -192,29 +184,41 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
myExtensions = Collections.unmodifiableList(myExtensions);
}
public abstract ChildTypeEnum getChildType();
@Override
public String toString() {
return getClass().getSimpleName()+"[" + getName() + ", " + getImplementingClass().getSimpleName() + "]";
}
protected void validateSealed() {
/*
* this does nothing, but BaseRuntimeElementCompositeDefinition
* overrides this method to provide functionality because that class
* defers the dealing process
*/
}
public enum ChildTypeEnum {
COMPOSITE_DATATYPE, PRIMITIVE_DATATYPE, RESOURCE, RESOURCE_BLOCK,
/**
COMPOSITE_DATATYPE, /**
* HL7.org structure style.
*/
CONTAINED_RESOURCE_LIST, /**
* HAPI structure style.
*/
CONTAINED_RESOURCES, EXTENSION_DECLARED,
ID_DATATYPE,
PRIMITIVE_DATATYPE, /**
* HAPI style.
*/
PRIMITIVE_XHTML,
UNDECL_EXT, EXTENSION_DECLARED,
/**
* HAPI structure style.
*/
CONTAINED_RESOURCES,
ID_DATATYPE,
/**
* HL7.org structure style.
*/
CONTAINED_RESOURCE_LIST,
/**
* HL7.org style.
*/
PRIMITIVE_XHTML_HL7ORG,
RESOURCE,
RESOURCE_BLOCK,
UNDECL_EXT,
}

View File

@ -22,11 +22,14 @@ package ca.uhn.fhir.context;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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 org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
@ -86,16 +89,20 @@ public class FhirContext {
private ArrayList<Class<? extends IBase>> myCustomTypes;
private Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<String, Class<? extends IBaseResource>>();
private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
private boolean myInitialized;
private HapiLocalizer myLocalizer = new HapiLocalizer();
private volatile Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinition = Collections.emptyMap();
private volatile Map<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap();
private volatile Map<String, Class<? extends IBaseResource>> myNameToResourceType;
private volatile INarrativeGenerator myNarrativeGenerator;
private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
private Set<PerformanceOptionsEnum> myPerformanceOptions = new HashSet<PerformanceOptionsEnum>();
private Collection<Class<? extends IBaseResource>> myResourceTypesToScan;
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private final IFhirVersion myVersion;
private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
private boolean myInitializing;
/**
* @deprecated It is recommended that you use one of the static initializer methods instead
@ -166,7 +173,7 @@ public class FhirContext {
ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name());
}
scanResourceTypes(toElementList(theResourceTypes));
myResourceTypesToScan = theResourceTypes;
}
private String createUnknownResourceNameError(String theResourceName, FhirVersionEnum theVersion) {
@ -191,12 +198,18 @@ public class FhirContext {
return myAddProfileTagWhenEncoding;
}
Collection<RuntimeResourceDefinition> getAllResourceDefinitions() {
validateInitialized();
return myNameToResourceDefinition.values();
}
/**
* Returns the default resource type for the given profile
*
* @see #setDefaultTypeForProfile(String, Class)
*/
public Class<? extends IBaseResource> getDefaultTypeForProfile(String theProfile) {
validateInitialized();
return myDefaultTypeForProfile.get(theProfile);
}
@ -206,6 +219,7 @@ public class FhirContext {
*/
@SuppressWarnings("unchecked")
public BaseRuntimeElementDefinition<?> getElementDefinition(Class<? extends IBase> theElementType) {
validateInitialized();
BaseRuntimeElementDefinition<?> retVal = myClassToElementDefinition.get(theElementType);
if (retVal == null) {
retVal = scanDatatype((Class<? extends IElement>) theElementType);
@ -221,11 +235,13 @@ public class FhirContext {
* </p>
*/
public BaseRuntimeElementDefinition<?> getElementDefinition(String theElementName) {
validateInitialized();
return myNameToElementDefinition.get(theElementName.toLowerCase());
}
/** For unit tests only */
int getElementDefinitionCount() {
validateInitialized();
return myClassToElementDefinition.size();
}
@ -233,6 +249,7 @@ public class FhirContext {
* Returns all element definitions (resources, datatypes, etc.)
*/
public Collection<BaseRuntimeElementDefinition<?>> getElementDefinitions() {
validateInitialized();
return Collections.unmodifiableCollection(myClassToElementDefinition.values());
}
@ -251,12 +268,20 @@ public class FhirContext {
return myNarrativeGenerator;
}
/**
* Get the configured performance options
*/
public Set<PerformanceOptionsEnum> getPerformanceOptions() {
return myPerformanceOptions;
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
* for extending the core library.
*/
@SuppressWarnings("unchecked")
public RuntimeResourceDefinition getResourceDefinition(Class<? extends IBaseResource> theResourceType) {
validateInitialized();
if (theResourceType == null) {
throw new NullPointerException("theResourceType can not be null");
}
@ -273,6 +298,7 @@ public class FhirContext {
public RuntimeResourceDefinition getResourceDefinition(FhirVersionEnum theVersion, String theResourceName) {
Validate.notNull(theVersion, "theVersion can not be null");
validateInitialized();
if (theVersion.equals(myVersion.getVersion())) {
return getResourceDefinition(theResourceName);
@ -302,6 +328,7 @@ public class FhirContext {
* for extending the core library.
*/
public RuntimeResourceDefinition getResourceDefinition(IBaseResource theResource) {
validateInitialized();
Validate.notNull(theResource, "theResource must not be null");
return getResourceDefinition(theResource.getClass());
}
@ -315,6 +342,7 @@ public class FhirContext {
*/
@SuppressWarnings("unchecked")
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
validateInitialized();
Validate.notBlank(theResourceName, "theResourceName must not be blank");
String resourceName = theResourceName.toLowerCase();
@ -338,6 +366,7 @@ public class FhirContext {
* for extending the core library.
*/
public RuntimeResourceDefinition getResourceDefinitionById(String theId) {
validateInitialized();
return myIdToResourceDefinition.get(theId);
}
@ -345,7 +374,8 @@ public class FhirContext {
* Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the
* core library.
*/
public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
public Collection<RuntimeResourceDefinition> getResourceDefinitionsWithExplicitId() {
validateInitialized();
return myIdToResourceDefinition.values();
}
@ -363,6 +393,7 @@ public class FhirContext {
}
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
validateInitialized();
return myRuntimeChildUndeclaredExtensionDefinition;
}
@ -378,6 +409,7 @@ public class FhirContext {
* @see #getDefaultTypeForProfile(String)
*/
public boolean hasDefaultTypeForProfile() {
validateInitialized();
return !myDefaultTypeForProfile.isEmpty();
}
@ -538,7 +570,8 @@ public class FhirContext {
return (RuntimeResourceDefinition) defs.get(theResourceType);
}
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IElement>> theResourceTypes) {
private synchronized Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IElement>> theResourceTypes) {
myInitializing = true;
List<Class<? extends IBase>> typesToScan = new ArrayList<Class<? extends IBase>>();
if (theResourceTypes != null) {
@ -586,6 +619,7 @@ public class FhirContext {
myNameToResourceType = scanner.getNameToResourceType();
myInitialized = true;
return classToElementDefinition;
}
@ -659,6 +693,31 @@ public class FhirContext {
myParserErrorHandler = theParserErrorHandler;
}
/**
* Sets the configured performance options
*
* @see PerformanceOptionsEnum for a list of available options
*/
public void setPerformanceOptions(Collection<PerformanceOptionsEnum> theOptions) {
myPerformanceOptions.clear();
if (theOptions != null) {
myPerformanceOptions.addAll(theOptions);
}
}
/**
* Sets the configured performance options
*
* @see PerformanceOptionsEnum for a list of available options
*/
public void setPerformanceOptions(PerformanceOptionsEnum... thePerformanceOptions) {
Collection<PerformanceOptionsEnum> asList = null;
if (thePerformanceOptions != null) {
asList = Arrays.asList(thePerformanceOptions);
}
setPerformanceOptions(asList);
}
/**
* Set the restful client factory
*
@ -681,6 +740,12 @@ public class FhirContext {
return resTypes;
}
private void validateInitialized() {
if (!myInitialized && !myInitializing) {
scanResourceTypes(toElementList(myResourceTypesToScan));
}
}
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU1 DSTU1}
*/

View File

@ -118,7 +118,7 @@ class ModelScanner {
myScanAlso.add(theType);
}
private Class<?> determineElementType(Field next) {
static Class<?> determineElementType(Field next) {
Class<?> nextElementType = next.getType();
if (List.class.equals(nextElementType)) {
nextElementType = ReflectionUtil.getGenericCollectionTypeOfField(next);
@ -129,7 +129,7 @@ class ModelScanner {
}
@SuppressWarnings("unchecked")
private IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
static IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
Class<?> bound = getGenericCollectionTypeOfCodedField(theNext);
if (bound == null) {
throw new ConfigurationException("Field '" + theNext + "' has no parameter for " + BoundCodeDt.class.getSimpleName() + " to determine enum type");
@ -214,7 +214,16 @@ class ModelScanner {
continue;
}
BaseRuntimeElementDefinition<?> next = nextEntry.getValue();
next.sealAndInitialize(myContext, myClassToElementDefinitions);
boolean deferredSeal = false;
if (myContext.getPerformanceOptions().contains(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING)) {
if (next instanceof BaseRuntimeElementCompositeDefinition) {
deferredSeal = true;
}
}
if (!deferredSeal) {
next.sealAndInitialize(myContext, myClassToElementDefinitions);
}
}
myRuntimeChildUndeclaredExtensionDefinition = new RuntimeChildUndeclaredExtensionDefinition();
@ -235,7 +244,7 @@ class ModelScanner {
* 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
* Proxy to simulate the HAPI annotations if the HL7.org ones are found instead.
*/
private <T extends Annotation> T pullAnnotation(AnnotatedElement theTarget, Class<T> theAnnotationType) {
static <T extends Annotation> T pullAnnotation(AnnotatedElement theTarget, Class<T> theAnnotationType) {
T retVal = theTarget.getAnnotation(theAnnotationType);
return retVal;
}
@ -298,366 +307,35 @@ class ModelScanner {
throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
}
RuntimeResourceBlockDefinition resourceDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass));
myClassToElementDefinitions.put(theClass, resourceDef);
RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions);
myClassToElementDefinitions.put(theClass, blockDef);
scanCompositeElementForChildren(theClass, resourceDef);
scanCompositeElementForChildren(theClass, blockDef);
}
private void scanCompositeElementForChildren(Class<? extends IBase> theClass, Object theBlockDef) {
// TODO remove
}
private void scanCompositeDatatype(Class<? extends ICompositeType> theClass, DatatypeDef theDatatypeDefinition) {
ourLog.debug("Scanning datatype class: {}", theClass.getName());
RuntimeCompositeDatatypeDefinition resourceDef;
RuntimeCompositeDatatypeDefinition elementDef;
if (theClass.equals(ExtensionDt.class)) {
resourceDef = new RuntimeExtensionDtDefinition(theDatatypeDefinition, theClass, true);
elementDef = new RuntimeExtensionDtDefinition(theDatatypeDefinition, theClass, true, myContext, myClassToElementDefinitions);
// } else if (IBaseMetaType.class.isAssignableFrom(theClass)) {
// resourceDef = new RuntimeMetaDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
} else {
resourceDef = new RuntimeCompositeDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
elementDef = new RuntimeCompositeDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions);
}
myClassToElementDefinitions.put(theClass, resourceDef);
myNameToElementDefinitions.put(resourceDef.getName().toLowerCase(), resourceDef);
scanCompositeElementForChildren(theClass, resourceDef);
myClassToElementDefinitions.put(theClass, elementDef);
myNameToElementDefinitions.put(elementDef.getName().toLowerCase(), elementDef);
scanCompositeElementForChildren(theClass, elementDef);
}
@SuppressWarnings("unchecked")
private void scanCompositeElementForChildren(Class<? extends IBase> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition) {
Set<String> elementNames = new HashSet<String>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
LinkedList<Class<? extends IBase>> classes = new LinkedList<Class<? extends IBase>>();
/*
* We scan classes for annotated fields in the class but also all of its superclasses
*/
Class<? extends IBase> current = theClass;
Map<String, Integer> forcedOrder = null;
do {
if (forcedOrder == null) {
ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
if (childOrder != null) {
forcedOrder = new HashMap<String, Integer>();
for (int i = 0; i < childOrder.names().length; i++) {
forcedOrder.put(childOrder.names()[i], i);
}
}
}
classes.push(current);
if (IBase.class.isAssignableFrom(current.getSuperclass())) {
current = (Class<? extends IBase>) current.getSuperclass();
} else {
current = null;
}
} while (current != null);
for (Class<? extends IBase> next : classes) {
scanCompositeElementForChildren(next, elementNames, orderToElementDef, orderToExtensionDef);
}
if (forcedOrder != null) {
/*
* Find out how many elements don't match any entry in the list
* for forced order. Those elements come first.
*/
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
int unknownCount = 0;
for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
if (!forcedOrder.containsKey(nextEntry.getElementName())) {
newOrderToExtensionDef.put(unknownCount, nextEntry);
unknownCount++;
}
}
for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
if (forcedOrder.containsKey(nextEntry.getElementName())) {
Integer newOrder = forcedOrder.get(nextEntry.getElementName());
newOrderToExtensionDef.put(newOrder + unknownCount, nextEntry);
}
}
orderToElementDef = newOrderToExtensionDef;
}
// while (orderToElementDef.size() > 0 && orderToElementDef.firstKey() <
// 0) {
// BaseRuntimeDeclaredChildDefinition elementDef =
// orderToElementDef.remove(orderToElementDef.firstKey());
// if (elementDef.getElementName().equals("identifier")) {
// orderToElementDef.put(theIdentifierOrder, elementDef);
// } else {
// throw new ConfigurationException("Don't know how to handle element: "
// + elementDef.getElementName());
// }
// }
TreeSet<Integer> orders = new TreeSet<Integer>();
orders.addAll(orderToElementDef.keySet());
orders.addAll(orderToExtensionDef.keySet());
for (Integer i : orders) {
BaseRuntimeChildDefinition nextChild = orderToElementDef.get(i);
if (nextChild != null) {
theDefinition.addChild(nextChild);
}
BaseRuntimeDeclaredChildDefinition nextExt = orderToExtensionDef.get(i);
if (nextExt != null) {
theDefinition.addExtension((RuntimeChildDeclaredExtensionDefinition) nextExt);
}
}
}
@SuppressWarnings("unchecked")
private void scanCompositeElementForChildren(Class<? extends IBase> theClass, Set<String> elementNames, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef,
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
int baseElementOrder = theOrderToElementDef.isEmpty() ? 0 : theOrderToElementDef.lastEntry().getKey() + 1;
for (Field next : theClass.getDeclaredFields()) {
if (Modifier.isFinal(next.getModifiers())) {
ourLog.trace("Ignoring constant {} on target type {}", next.getName(), theClass);
continue;
}
Child childAnnotation = pullAnnotation(next, Child.class);
if (childAnnotation == null) {
ourLog.trace("Ignoring non @Child field {} on target type {}", next.getName(), theClass);
continue;
}
Description descriptionAnnotation = pullAnnotation(next, Description.class);
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
Extension extensionAttr = pullAnnotation(next, Extension.class);
if (extensionAttr != null) {
orderMap = theOrderToExtensionDef;
}
String elementName = childAnnotation.name();
int order = childAnnotation.order();
boolean childIsChoiceType = false;
if (order == Child.REPLACE_PARENT) {
if (extensionAttr != null) {
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
if (nextDef instanceof RuntimeChildDeclaredExtensionDefinition) {
if (nextDef.getExtensionUrl().equals(extensionAttr.url())) {
order = nextEntry.getKey();
orderMap.remove(nextEntry.getKey());
elementNames.remove(elementName);
break;
}
}
}
if (order == Child.REPLACE_PARENT) {
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() + " could be found on type " + next.getDeclaringClass().getSimpleName());
}
} else {
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
if (elementName.equals(nextDef.getElementName())) {
order = nextEntry.getKey();
BaseRuntimeDeclaredChildDefinition existing = orderMap.remove(nextEntry.getKey());
elementNames.remove(elementName);
/*
* See #350 - If the original field (in the superclass) with the given name is a choice, then we need to make sure
* that the field which replaces is a choice even if it's only a choice of one type - this is because the
* element name when serialized still needs to reflect the datatype
*/
if (existing instanceof RuntimeChildChoiceDefinition) {
childIsChoiceType = true;
}
break;
}
}
if (order == Child.REPLACE_PARENT) {
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 " + next.getDeclaringClass().getSimpleName());
}
}
}
if (order < 0 && order != Child.ORDER_UNKNOWN) {
throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + next.getName() + "' on target type: " + theClass);
}
if (order != Child.ORDER_UNKNOWN) {
order = order + baseElementOrder;
}
// int min = childAnnotation.min();
// int max = childAnnotation.max();
/*
* Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict with any given IDs and can be figured out later
*/
if (order == Child.ORDER_UNKNOWN) {
order = Integer.valueOf(0);
while (orderMap.containsKey(order)) {
order++;
}
}
List<Class<? extends IBase>> choiceTypes = new ArrayList<Class<? extends IBase>>();
for (Class<? extends IBase> nextChoiceType : childAnnotation.type()) {
choiceTypes.add(nextChoiceType);
}
if (orderMap.containsKey(order)) {
throw new ConfigurationException("Detected duplicate field order '" + childAnnotation.order() + "' for element named '" + elementName + "' in type '" + theClass.getCanonicalName() + "' - Already had: " + orderMap.get(order).getElementName());
}
if (elementNames.contains(elementName)) {
throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '" + theClass.getCanonicalName() + "'");
}
Class<?> nextElementType = determineElementType(next);
if (childAnnotation.name().equals("extension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
RuntimeChildExtension def = new RuntimeChildExtension(next, childAnnotation.name(), childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (childAnnotation.name().equals("modifierExtension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
RuntimeChildExtension def = new RuntimeChildExtension(next, childAnnotation.name(), childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (BaseContainedDt.class.isAssignableFrom(nextElementType) || (childAnnotation.name().equals("contained") && IBaseResource.class.isAssignableFrom(nextElementType))) {
/*
* Child is contained resources
*/
RuntimeChildContainedResources def = new RuntimeChildContainedResources(next, childAnnotation, descriptionAnnotation, elementName);
orderMap.put(order, def);
} else if (IAnyResource.class.isAssignableFrom(nextElementType) || IResource.class.equals(nextElementType)) {
/*
* Child is a resource as a direct child, as in Bundle.entry.resource
*/
RuntimeChildDirectResource def = new RuntimeChildDirectResource(next, childAnnotation, descriptionAnnotation, elementName);
orderMap.put(order, def);
} else {
childIsChoiceType |= choiceTypes.size() > 1;
if (childIsChoiceType && !BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) && !IBaseReference.class.isAssignableFrom(nextElementType)) {
/*
* Child is a choice element
*/
for (Class<? extends IBase> nextType : choiceTypes) {
addScanAlso(nextType);
}
RuntimeChildChoiceDefinition def = new RuntimeChildChoiceDefinition(next, elementName, childAnnotation, descriptionAnnotation, choiceTypes);
orderMap.put(order, def);
} else if (next.getType().equals(ExtensionDt.class)) {
RuntimeChildExtensionDt def = new RuntimeChildExtensionDt(next, elementName, childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
if (IElement.class.isAssignableFrom(nextElementType)) {
addScanAlso((Class<? extends IElement>) nextElementType);
}
} else if (extensionAttr != null) {
/*
* Child is an extension
*/
Class<? extends IBase> et = (Class<? extends IBase>) nextElementType;
Object binder = null;
if (BoundCodeDt.class.isAssignableFrom(nextElementType) || IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
binder = getBoundCodeBinder(next);
}
RuntimeChildDeclaredExtensionDefinition def = new RuntimeChildDeclaredExtensionDefinition(next, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et,
binder);
if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
def.setEnumerationType(ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next));
}
orderMap.put(order, def);
if (IBase.class.isAssignableFrom(nextElementType)) {
addScanAlso((Class<? extends IBase>) nextElementType);
}
} else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) || IBaseReference.class.isAssignableFrom(nextElementType)) {
/*
* Child is a resource reference
*/
List<Class<? extends IBaseResource>> refTypesList = new ArrayList<Class<? extends IBaseResource>>();
for (Class<? extends IElement> nextType : childAnnotation.type()) {
if (IBaseReference.class.isAssignableFrom(nextType)) {
refTypesList.add(myVersion.isRi() ? IAnyResource.class : IResource.class);
continue;
} else if (IBaseResource.class.isAssignableFrom(nextType) == false) {
throw new ConfigurationException("Field '" + next.getName() + "' in class '" + next.getDeclaringClass().getCanonicalName() + "' is of type " + BaseResourceReferenceDt.class + " but contains a non-resource type: " + nextType.getCanonicalName());
}
refTypesList.add((Class<? extends IBaseResource>) nextType);
addScanAlso(nextType);
}
RuntimeChildResourceDefinition def = new RuntimeChildResourceDefinition(next, elementName, childAnnotation, descriptionAnnotation, refTypesList);
orderMap.put(order, def);
} else if (IResourceBlock.class.isAssignableFrom(nextElementType) || IBaseBackboneElement.class.isAssignableFrom(nextElementType)
|| IBaseDatatypeElement.class.isAssignableFrom(nextElementType)) {
/*
* Child is a resource block (i.e. a sub-tag within a resource) TODO: do these have a better name according to HL7?
*/
Class<? extends IBase> blockDef = (Class<? extends IBase>) nextElementType;
addScanAlso(blockDef);
RuntimeChildResourceBlockDefinition def = new RuntimeChildResourceBlockDefinition(next, childAnnotation, descriptionAnnotation, elementName, blockDef);
orderMap.put(order, def);
} else if (IDatatype.class.equals(nextElementType) || IElement.class.equals(nextElementType) || "Type".equals(nextElementType.getSimpleName())
|| IBaseDatatype.class.equals(nextElementType)) {
RuntimeChildAny def = new RuntimeChildAny(next, elementName, childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (IDatatype.class.isAssignableFrom(nextElementType) || IPrimitiveType.class.isAssignableFrom(nextElementType) || ICompositeType.class.isAssignableFrom(nextElementType)
|| IBaseDatatype.class.isAssignableFrom(nextElementType) || IBaseExtension.class.isAssignableFrom(nextElementType)) {
Class<? extends IBase> nextDatatype = (Class<? extends IBase>) nextElementType;
addScanAlso(nextDatatype);
BaseRuntimeChildDatatypeDefinition def;
if (IPrimitiveType.class.isAssignableFrom(nextElementType)) {
if (nextElementType.equals(BoundCodeDt.class)) {
IValueSetEnumBinder<Enum<?>> binder = getBoundCodeBinder(next);
Class<? extends Enum<?>> enumType = determineEnumTypeForBoundField(next);
def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
} else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
Class<? extends Enum<?>> binderType = determineEnumTypeForBoundField(next);
def = new RuntimeChildPrimitiveEnumerationDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binderType);
} else {
def = new RuntimeChildPrimitiveDatatypeDefinition(next, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
}
} else if (IBaseXhtml.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildXhtmlDatatypeDefinition(next, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
} else {
if (IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
IValueSetEnumBinder<Enum<?>> binder = getBoundCodeBinder(next);
Class<? extends Enum<?>> enumType = determineEnumTypeForBoundField(next);
def = new RuntimeChildCompositeBoundDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
} else if (BaseNarrativeDt.class.isAssignableFrom(nextElementType) || INarrative.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildNarrativeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
} else {
def = new RuntimeChildCompositeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
}
}
orderMap.put(order, def);
} else {
throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a valid child type: " + nextElementType);
}
}
elementNames.add(elementName);
}
}
private Class<? extends Enum<?>> determineEnumTypeForBoundField(Field next) {
static Class<? extends Enum<?>> determineEnumTypeForBoundField(Field next) {
@SuppressWarnings("unchecked")
Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next);
return enumType;
@ -671,28 +349,28 @@ class ModelScanner {
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
}
BaseRuntimeElementDefinition<?> resourceDef;
BaseRuntimeElementDefinition<?> elementDef;
if (theClass.equals(XhtmlDt.class)) {
@SuppressWarnings("unchecked")
Class<XhtmlDt> clazz = (Class<XhtmlDt>) theClass;
resourceDef = new RuntimePrimitiveDatatypeNarrativeDefinition(resourceName, clazz, isStandardType(clazz));
elementDef = new RuntimePrimitiveDatatypeNarrativeDefinition(resourceName, clazz, isStandardType(clazz));
} else if (IBaseXhtml.class.isAssignableFrom(theClass)) {
@SuppressWarnings("unchecked")
Class<? extends IBaseXhtml> clazz = (Class<? extends IBaseXhtml>) theClass;
resourceDef = new RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition(resourceName, clazz, isStandardType(clazz));
elementDef = new RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition(resourceName, clazz, isStandardType(clazz));
} else if (IIdType.class.isAssignableFrom(theClass)) {
resourceDef = new RuntimeIdDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
elementDef = new RuntimeIdDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
} else {
resourceDef = new RuntimePrimitiveDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
elementDef = new RuntimePrimitiveDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
}
myClassToElementDefinitions.put(theClass, resourceDef);
myClassToElementDefinitions.put(theClass, elementDef);
if (!theDatatypeDefinition.isSpecialization()) {
if (myVersion.isRi() && IDatatype.class.isAssignableFrom(theClass)) {
ourLog.debug("Not adding non RI type {} to RI context", theClass);
} else if (!myVersion.isRi() && !IDatatype.class.isAssignableFrom(theClass)) {
ourLog.debug("Not adding RI type {} to non RI context", theClass);
} else {
myNameToElementDefinitions.put(resourceName, resourceDef);
myNameToElementDefinitions.put(resourceName, elementDef);
}
}
@ -736,7 +414,7 @@ class ModelScanner {
}
}
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(myContext, resourceName, theClass, resourceDefinition, standardType);
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(myContext, resourceName, theClass, resourceDefinition, standardType, myClassToElementDefinitions);
myClassToElementDefinitions.put(theClass, resourceDef);
if (primaryNameProvider) {
if (resourceDef.getStructureVersion() == myVersion) {

View File

@ -0,0 +1,20 @@
package ca.uhn.fhir.context;
/**
* This enum contains options to be used for {@link FhirContext#setPerformanceOptions(PerformanceOptionsEnum...)}
*/
public enum PerformanceOptionsEnum {
/**
* When this option is set, model classes will not be scanned for children until the
* child list for the given type is actually accessed.
* <p>
* The effect of this option is that reflection operations to scan children will be
* deferred, and some may never happen if specific model types aren't actually used.
* This option is useful on environments where reflection is particularly slow, e.g.
* Android or low powered devices.
* </p>
*/
DEFERRED_MODEL_SCANNING
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.context;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.emptyCollectionOf;
import java.lang.reflect.Field;
import java.util.ArrayList;
@ -158,6 +159,15 @@ public class RuntimeChildDeclaredExtensionDefinition extends BaseRuntimeDeclared
myUrlToChildExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(myChildType);
/*
* This will happen for any type that isn't defined in the base set of
* built-in types, e.g. custom structures or custom extensions
*/
if (elementDef == null) {
elementDef = theContext.getElementDefinition(myChildType);
}
if (elementDef instanceof RuntimePrimitiveDatatypeDefinition || elementDef instanceof RuntimeCompositeDatatypeDefinition) {
myDatatypeChildName = "value" + elementDef.getName().substring(0, 1).toUpperCase() + elementDef.getName().substring(1);
if ("valueResourceReference".equals(myDatatypeChildName)) {

View File

@ -34,21 +34,27 @@ public class RuntimeChildResourceBlockDefinition extends BaseRuntimeDeclaredChil
private RuntimeResourceBlockDefinition myElementDef;
private Class<? extends IBase> myResourceBlockType;
private FhirContext myContext;
public RuntimeChildResourceBlockDefinition(Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName, Class<? extends IBase> theResourceBlockType) throws ConfigurationException {
public RuntimeChildResourceBlockDefinition(FhirContext theContext, Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName, Class<? extends IBase> theResourceBlockType) throws ConfigurationException {
super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName);
myContext = theContext;
myResourceBlockType = theResourceBlockType;
}
@Override
public RuntimeResourceBlockDefinition getChildByName(String theName) {
if (getElementName().equals(theName)) {
return myElementDef;
return getDefinition();
}else {
return null;
}
}
private RuntimeResourceBlockDefinition getDefinition() {
return (RuntimeResourceBlockDefinition) myContext.getElementDefinition(myResourceBlockType);
}
@Override
public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
if (myResourceBlockType.equals(theDatatype)) {
@ -60,7 +66,7 @@ public class RuntimeChildResourceBlockDefinition extends BaseRuntimeDeclaredChil
@Override
public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) {
if (myResourceBlockType.equals(theDatatype)) {
return myElementDef;
return getDefinition();
}
return null;
}
@ -72,7 +78,7 @@ public class RuntimeChildResourceBlockDefinition extends BaseRuntimeDeclaredChil
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
myElementDef = (RuntimeResourceBlockDefinition) theClassToElementDefinitions.get(myResourceBlockType);
// myElementDef = (RuntimeResourceBlockDefinition) theClassToElementDefinitions.get(myResourceBlockType);
}
}

View File

@ -36,8 +36,8 @@ public class RuntimeCompositeDatatypeDefinition extends BaseRuntimeElementCompos
private Class<? extends IBaseDatatype> myProfileOfType;
private BaseRuntimeElementDefinition<?> myProfileOf;
public RuntimeCompositeDatatypeDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType) {
super(theDef.name(), theImplementingClass, theStandardType);
public RuntimeCompositeDatatypeDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theDef.name(), theImplementingClass, theStandardType, theContext, theClassToElementDefinitions);
String resourceName = theDef.name();
if (isBlank(resourceName)) {
@ -81,6 +81,7 @@ public class RuntimeCompositeDatatypeDefinition extends BaseRuntimeElementCompos
@Override
public boolean isProfileOf(Class<? extends IBaseDatatype> theType) {
validateSealed();
if (myProfileOfType != null) {
if (myProfileOfType.equals(theType)) {
return true;

View File

@ -34,8 +34,8 @@ public class RuntimeExtensionDtDefinition extends RuntimeCompositeDatatypeDefini
private List<BaseRuntimeChildDefinition> myChildren;
public RuntimeExtensionDtDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType) {
super(theDef, theImplementingClass, theStandardType);
public RuntimeExtensionDtDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theDef, theImplementingClass, theStandardType, theContext, theClassToElementDefinitions);
}
@Override

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.context;
import java.util.Map;
/*
* #%L
* HAPI FHIR - Core Library
@ -24,8 +26,8 @@ import org.hl7.fhir.instance.model.api.IBase;
public class RuntimeResourceBlockDefinition extends BaseRuntimeElementCompositeDefinition<IBase> {
public RuntimeResourceBlockDefinition(String theName, Class<? extends IBase> theImplementingClass, boolean theStandardType) {
super(theName, theImplementingClass, theStandardType);
public RuntimeResourceBlockDefinition(String theName, Class<? extends IBase> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theName, theImplementingClass, theStandardType, theContext, theClassToElementDefinitions);
}
@Override

View File

@ -36,7 +36,7 @@ import ca.uhn.fhir.util.UrlUtil;
public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IBaseResource> {
private RuntimeResourceDefinition myBaseDefinition;
private Class<? extends IBaseResource> myBaseType;
private Map<String, List<RuntimeSearchParam>> myCompartmentNameToSearchParams;
private FhirContext myContext;
private String myId;
@ -45,9 +45,10 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
private String myResourceProfile;
private List<RuntimeSearchParam> mySearchParams;
private final FhirVersionEnum myStructureVersion;
private volatile RuntimeResourceDefinition myBaseDefinition;
public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class<? extends IBaseResource> theClass, ResourceDef theResourceAnnotation, boolean theStandardType) {
super(theResourceName, theClass, theStandardType);
public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class<? extends IBaseResource> theClass, ResourceDef theResourceAnnotation, boolean theStandardType, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theResourceName, theClass, theStandardType, theContext, theClassToElementDefinitions);
myContext = theContext;
myResourceProfile = theResourceAnnotation.profile();
myId = theResourceAnnotation.id();
@ -76,6 +77,10 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
* </p>
*/
public RuntimeResourceDefinition getBaseDefinition() {
validateSealed();
if (myBaseDefinition == null) {
myBaseDefinition = myContext.getResourceDefinition(myBaseType);
}
return myBaseDefinition;
}
@ -105,6 +110,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
}
public String getResourceProfile(String theServerBase) {
validateSealed();
String profile;
if (!myResourceProfile.isEmpty()) {
profile = myResourceProfile;
@ -128,10 +134,12 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
}
public RuntimeSearchParam getSearchParam(String theName) {
validateSealed();
return myNameToSearchParam.get(theName);
}
public List<RuntimeSearchParam> getSearchParams() {
validateSealed();
return mySearchParams;
}
@ -139,6 +147,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
* Will not return null
*/
public List<RuntimeSearchParam> getSearchParamsForCompartmentName(String theCompartmentName) {
validateSealed();
List<RuntimeSearchParam> retVal = myCompartmentNameToSearchParams.get(theCompartmentName);
if (retVal == null) {
return Collections.emptyList();
@ -154,6 +163,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
return "Bundle".equals(getName());
}
@SuppressWarnings("unchecked")
@Override
public void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super.sealAndInitialize(theContext, theClassToElementDefinitions);
@ -183,17 +193,18 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams);
Class<?> target = getImplementingClass();
myBaseDefinition = this;
myBaseType = (Class<? extends IBaseResource>) target;
do {
target = target.getSuperclass();
if (IBaseResource.class.isAssignableFrom(target) && target.getAnnotation(ResourceDef.class) != null) {
myBaseDefinition = (RuntimeResourceDefinition) theClassToElementDefinitions.get(target);
myBaseType = (Class<? extends IBaseResource>) target;
}
} while (target.equals(Object.class) == false);
}
@Deprecated
public synchronized IBaseResource toProfile() {
validateSealed();
if (myProfileDef != null) {
return myProfileDef;
}
@ -205,6 +216,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
}
public synchronized IBaseResource toProfile(String theServerBase) {
validateSealed();
if (myProfileDef != null) {
return myProfileDef;
}

View File

@ -216,6 +216,10 @@ public interface IRestfulClientFactory {
* validation involves the client requesting the server's conformance statement
* to determine whether the server is appropriate for the given client.
* <p>
* This check is primarily to validate that the server supports an appropriate
* version of FHIR
* </p>
* <p>
* The default value for this setting is defined by {@link #DEFAULT_SERVER_VALIDATION_MODE}
* </p>
*

View File

@ -37,6 +37,8 @@ import javax.persistence.criteria.Root;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
@ -45,8 +47,10 @@ import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.util.ReindexFailureException;
import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.model.api.TagList;
@ -68,6 +72,9 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
@Autowired
private IForcedIdDao myForcedIdDao;
@Autowired
private ITermConceptDao myTermConceptDao;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void deleteAllTagsOnServer(RequestDetails theRequestDetails) {
@ -81,6 +88,12 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
private int doPerformReindexingPass(final Integer theCount, final RequestDetails theRequestDetails) {
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
int retVal = doPerformReindexingPassForResources(theCount, theRequestDetails, txTemplate);
retVal += doPerformReindexingPassForConcepts(txTemplate);
return retVal;
}
private int doPerformReindexingPassForResources(final Integer theCount, final RequestDetails theRequestDetails, TransactionTemplate txTemplate) {
return txTemplate.execute(new TransactionCallback<Integer>() {
@SuppressWarnings("unchecked")
@Override
@ -106,8 +119,7 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
for (ResourceTable resourceTable : resources) {
try {
/*
* This part is because from HAPI 1.5 - 1.6 we changed the format of
* forced ID to be "type/id" instead of just "id"
* This part is because from HAPI 1.5 - 1.6 we changed the format of forced ID to be "type/id" instead of just "id"
*/
ForcedId forcedId = resourceTable.getForcedId();
if (forcedId != null) {
@ -140,6 +152,37 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
});
}
private int doPerformReindexingPassForConcepts(TransactionTemplate txTemplate) {
return txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
int maxResult = 10000;
Page<TermConcept> resources = myTermConceptDao.findResourcesRequiringReindexing(new PageRequest(0, maxResult));
if (resources.hasContent() == false) {
return 0;
}
ourLog.info("Indexing {} / {} concepts", resources.getContent().size(), resources.getTotalElements());
int count = 0;
long start = System.currentTimeMillis();
for (TermConcept resourceTable : resources) {
resourceTable.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED);
myTermConceptDao.save(resourceTable);
count++;
}
long delay = System.currentTimeMillis() - start;
long avg = (delay / resources.getContent().size());
ourLog.info("Indexed {} / {} concepts in {}ms - Avg {}ms / resource", new Object[] { count, resources.getContent().size(), delay, avg });
return resources.getContent().size();
}
});
}
@Override
public TagList getAllTags(RequestDetails theRequestDetails) {
// Notify interceptors
@ -194,7 +237,9 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
@Transactional()
@Override
public int markAllResourcesForReindexing() {
return myEntityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " t SET t.myIndexStatus = null").executeUpdate();
int retVal = myEntityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " t SET t.myIndexStatus = null").executeUpdate();
retVal += myTermConceptDao.markAllForReindexing();
return retVal;
}
private void markResourceAsIndexingFailed(final long theId) {

View File

@ -2,6 +2,9 @@ package ca.uhn.fhir.jpa.dao.data;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/*
* #%L
* HAPI FHIR JPA Server
@ -42,4 +45,11 @@ public interface ITermConceptDao extends JpaRepository<TermConcept, Long> {
@Modifying
void deleteByCodeSystemVersion(@Param("cs_pid") Long thePid);
@Query("UPDATE TermConcept t SET t.myIndexStatus = null")
@Modifying
int markAllForReindexing();
@Query("SELECT t FROM TermConcept t WHERE t.myIndexStatus = null")
Page<TermConcept> findResourcesRequiringReindexing(Pageable thePageRequest);
}

View File

@ -39,6 +39,7 @@ import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent;
@ -71,8 +72,7 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet>
@Override
public ValueSet expand(IIdType theId, String theFilter) {
ValueSet source = myValidationSupport.fetchResource(getContext(), ValueSet.class, theId.getValue());
ValueSet retVal = doExpand(source);
return retVal;
return expand(source, theFilter);
}
private ValueSet doExpand(ValueSet theSource) {
@ -135,11 +135,35 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet>
@Override
public ValueSet expand(ValueSet source, String theFilter) {
ValueSet retVal = doExpand(source);
ValueSet toExpand = new ValueSet();
for (UriType next : source.getCompose().getImport()) {
ConceptSetComponent include = toExpand.getCompose().addInclude();
include.setSystem(next.getValue());
addFilterIfPresent(theFilter, include);
}
for (ConceptSetComponent next : source.getCompose().getInclude()) {
toExpand.getCompose().addInclude(next);
addFilterIfPresent(theFilter, next);
}
if (toExpand.getCompose().isEmpty()) {
throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
}
toExpand.getCompose().getExclude().addAll(source.getCompose().getExclude());
ValueSet retVal = doExpand(toExpand);
return retVal;
}
private void addFilterIfPresent(String theFilter, ConceptSetComponent include) {
if (isNotBlank(theFilter)) {
include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue(theFilter);
}
}
@Override
public ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) {

View File

@ -36,6 +36,7 @@ import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
@ -63,6 +64,8 @@ import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
@Indexed()
@Table(name="TRM_CONCEPT", uniqueConstraints= {
@UniqueConstraint(name="IDX_CONCEPT_CS_CODE", columnNames= {"CODESYSTEM_PID", "CODE"})
}, indexes= {
@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList="INDEX_STATUS")
})
//@formatter:on
public class TermConcept implements Serializable {
@ -83,6 +86,12 @@ public class TermConcept implements Serializable {
@JoinColumn(name="CODESYSTEM_PID", referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_CONCEPT_PID_CS_PID"))
private TermCodeSystemVersion myCodeSystem;
@Column(name="CODESYSTEM_PID", insertable=false, updatable=false)
@Fields({
@Field(name="myCodeSystemVersionPid")
})
private long myCodeSystemVersionPid;
//@formatter:off
@Column(name="DISPLAY", length=MAX_DESC_LENGTH, nullable=true)
@Fields({
@ -100,6 +109,9 @@ public class TermConcept implements Serializable {
@Column(name="PID")
private Long myId;
@Column(name = "INDEX_STATUS", nullable = true)
private Long myIndexStatus;
@Transient
@Fields({
@Field(name = "myParentPids", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
@ -109,12 +121,6 @@ public class TermConcept implements Serializable {
@OneToMany(cascade= {}, fetch=FetchType.LAZY, mappedBy="myChild")
private Collection<TermConceptParentChildLink> myParents;
@Column(name="CODESYSTEM_PID", insertable=false, updatable=false)
@Fields({
@Field(name="myCodeSystemVersionPid")
})
private long myCodeSystemVersionPid;
public TermConcept() {
super();
}
@ -216,6 +222,10 @@ public class TermConcept implements Serializable {
return this;
}
public void setIndexStatus(Long theIndexStatus) {
myIndexStatus = theIndexStatus;
}
public void setParentPids(Set<Long> theParentPids) {
StringBuilder b = new StringBuilder();
for (Long next : theParentPids) {

View File

@ -33,17 +33,16 @@ import org.hl7.fhir.dstu3.model.DecimalType;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.dstu3.model.StringType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Transaction;
@ -63,99 +62,99 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProvider<Bundle, Meta>
//@formatter:off
// This is generated by hand:
// ls hapi-fhir-structures-dstu2/target/generated-sources/tinder/ca/uhn/fhir/model/dstu2/resource/ | sort | sed "s/.java//" | sed "s/^/@OperationParam(name=\"/" | sed "s/$/\", type=IntegerDt.class, min=0, max=1),/"
// ls hapi-fhir-structures-dstu2/target/generated-sources/tinder/ca/uhn/fhir/model/dstu2/resource/ | sort | sed "s/.java//" | sed "s/^/@OperationParam(name=\"/" | sed "s/$/\", type=IntegerType.class, min=0, max=1),/"
@Operation(name="$get-resource-counts", idempotent=true, returnParameters= {
@OperationParam(name="AllergyIntolerance", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Appointment", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="AppointmentResponse", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="AuditEvent", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Basic", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Binary", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="BodySite", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Bundle", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="CarePlan", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="CarePlan2", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Claim", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ClaimResponse", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ClinicalImpression", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Communication", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="CommunicationRequest", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Composition", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ConceptMap", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Condition", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Conformance", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Contract", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Contraindication", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Coverage", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DataElement", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Device", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DeviceComponent", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DeviceMetric", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DeviceUseRequest", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DeviceUseStatement", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DiagnosticOrder", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DiagnosticReport", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DocumentManifest", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="DocumentReference", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="EligibilityRequest", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="EligibilityResponse", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Encounter", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="EnrollmentRequest", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="EnrollmentResponse", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="EpisodeOfCare", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ExplanationOfBenefit", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="FamilyMemberHistory", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Flag", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Goal", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Group", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="HealthcareService", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ImagingObjectSelection", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ImagingStudy", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Immunization", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ImmunizationRecommendation", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ListResource", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Location", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Media", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Medication", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="MedicationAdministration", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="MedicationDispense", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="MedicationPrescription", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="MedicationStatement", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="MessageHeader", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="NamingSystem", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="NutritionOrder", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Observation", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="OperationDefinition", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="OperationOutcome", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Order", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="OrderResponse", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Organization", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Parameters", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Patient", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="PaymentNotice", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="PaymentReconciliation", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Person", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Practitioner", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Procedure", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ProcedureRequest", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ProcessRequest", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ProcessResponse", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Provenance", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Questionnaire", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="QuestionnaireAnswers", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ReferralRequest", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="RelatedPerson", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="RiskAssessment", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Schedule", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="SearchParameter", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Slot", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Specimen", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="StructureDefinition", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Subscription", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Substance", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="Supply", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="ValueSet", type=IntegerDt.class, min=0, max=1),
@OperationParam(name="VisionPrescription", type=IntegerDt.class, min=0, max=1)
@OperationParam(name="AllergyIntolerance", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Appointment", type=IntegerType.class, min=0, max=1),
@OperationParam(name="AppointmentResponse", type=IntegerType.class, min=0, max=1),
@OperationParam(name="AuditEvent", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Basic", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Binary", type=IntegerType.class, min=0, max=1),
@OperationParam(name="BodySite", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Bundle", type=IntegerType.class, min=0, max=1),
@OperationParam(name="CarePlan", type=IntegerType.class, min=0, max=1),
@OperationParam(name="CarePlan2", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Claim", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ClaimResponse", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ClinicalImpression", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Communication", type=IntegerType.class, min=0, max=1),
@OperationParam(name="CommunicationRequest", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Composition", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ConceptMap", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Condition", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Conformance", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Contract", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Contraindication", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Coverage", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DataElement", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Device", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DeviceComponent", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DeviceMetric", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DeviceUseRequest", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DeviceUseStatement", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DiagnosticOrder", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DiagnosticReport", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DocumentManifest", type=IntegerType.class, min=0, max=1),
@OperationParam(name="DocumentReference", type=IntegerType.class, min=0, max=1),
@OperationParam(name="EligibilityRequest", type=IntegerType.class, min=0, max=1),
@OperationParam(name="EligibilityResponse", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Encounter", type=IntegerType.class, min=0, max=1),
@OperationParam(name="EnrollmentRequest", type=IntegerType.class, min=0, max=1),
@OperationParam(name="EnrollmentResponse", type=IntegerType.class, min=0, max=1),
@OperationParam(name="EpisodeOfCare", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ExplanationOfBenefit", type=IntegerType.class, min=0, max=1),
@OperationParam(name="FamilyMemberHistory", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Flag", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Goal", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Group", type=IntegerType.class, min=0, max=1),
@OperationParam(name="HealthcareService", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ImagingObjectSelection", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ImagingStudy", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Immunization", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ImmunizationRecommendation", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ListResource", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Location", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Media", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Medication", type=IntegerType.class, min=0, max=1),
@OperationParam(name="MedicationAdministration", type=IntegerType.class, min=0, max=1),
@OperationParam(name="MedicationDispense", type=IntegerType.class, min=0, max=1),
@OperationParam(name="MedicationPrescription", type=IntegerType.class, min=0, max=1),
@OperationParam(name="MedicationStatement", type=IntegerType.class, min=0, max=1),
@OperationParam(name="MessageHeader", type=IntegerType.class, min=0, max=1),
@OperationParam(name="NamingSystem", type=IntegerType.class, min=0, max=1),
@OperationParam(name="NutritionOrder", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Observation", type=IntegerType.class, min=0, max=1),
@OperationParam(name="OperationDefinition", type=IntegerType.class, min=0, max=1),
@OperationParam(name="OperationOutcome", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Order", type=IntegerType.class, min=0, max=1),
@OperationParam(name="OrderResponse", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Organization", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Parameters", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Patient", type=IntegerType.class, min=0, max=1),
@OperationParam(name="PaymentNotice", type=IntegerType.class, min=0, max=1),
@OperationParam(name="PaymentReconciliation", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Person", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Practitioner", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Procedure", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ProcedureRequest", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ProcessRequest", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ProcessResponse", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Provenance", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Questionnaire", type=IntegerType.class, min=0, max=1),
@OperationParam(name="QuestionnaireAnswers", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ReferralRequest", type=IntegerType.class, min=0, max=1),
@OperationParam(name="RelatedPerson", type=IntegerType.class, min=0, max=1),
@OperationParam(name="RiskAssessment", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Schedule", type=IntegerType.class, min=0, max=1),
@OperationParam(name="SearchParameter", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Slot", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Specimen", type=IntegerType.class, min=0, max=1),
@OperationParam(name="StructureDefinition", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Subscription", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Substance", type=IntegerType.class, min=0, max=1),
@OperationParam(name="Supply", type=IntegerType.class, min=0, max=1),
@OperationParam(name="ValueSet", type=IntegerType.class, min=0, max=1),
@OperationParam(name="VisionPrescription", type=IntegerType.class, min=0, max=1)
})
@Description(shortDefinition="Provides the number of resources currently stored on the server, broken down by resource type")
//@formatter:on
@ -173,7 +172,7 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProvider<Bundle, Meta>
//@formatter:off
@Operation(name="$mark-all-resources-for-reindexing", idempotent=true, returnParameters= {
@OperationParam(name="count", type=IntegerDt.class)
@OperationParam(name="count", type=IntegerType.class)
})
//@formatter:on
public Parameters markAllResourcesForReindexing() {

View File

@ -39,6 +39,7 @@ import org.springframework.transaction.annotation.Transactional;
import com.google.common.base.Stopwatch;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
@ -209,10 +210,13 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
if (theConceptsStack.size() % 1000 == 0) {
float pct = (float) theConceptsStack.size() / (float) theTotalConcepts;
ourLog.info("Have saved {}/{} concepts - {}%", theConceptsStack.size(), theTotalConcepts, (int)( pct*100.0f));
ourLog.info("Have saved {}/{} concepts ({}%), flushing", theConceptsStack.size(), theTotalConcepts, (int)( pct*100.0f));
myConceptDao.flush();
myConceptParentChildLinkDao.flush();
}
theConcept.setCodeSystem(theCodeSystem);
theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED);
myConceptDao.save(theConcept);
for (TermConceptParentChildLink next : theConcept.getChildren()) {

View File

@ -98,7 +98,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
b.append("Removing circular reference code ");
b.append(nextChild.getCode());
b.append(" from parent ");
b.append(nextChild.getCode());
b.append(next.getParent().getCode());
b.append(". Chain was: ");
for (String nextInChain : theChain) {
TermConcept nextCode = theCode2concept.get(nextInChain);
@ -187,7 +187,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
private void iterateOverZipFile(Map<String, File> theFilenameToFile, String fileNamePart, IRecordHandler handler, char theDelimiter, QuoteMode theQuoteMode) {
boolean found = false;
for (Entry<String, File> nextEntry : theFilenameToFile.entrySet()) {
for (Entry<String, File> nextEntry : new HashMap<String, File>(theFilenameToFile).entrySet()) {
if (nextEntry.getKey().contains(fileNamePart)) {
ourLog.info("Processing file {}", nextEntry.getKey());
@ -217,6 +217,11 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
nextLoggedCount += logIncrement;
}
}
ourLog.info("Deleting temporary file: {}", nextEntry.getValue());
nextEntry.getValue().delete();
theFilenameToFile.remove(nextEntry.getKey());
} catch (IOException e) {
throw new InternalErrorException(e);
} finally {
@ -415,6 +420,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
private final class SctHandlerConcept implements IRecordHandler {
private Set<String> myValidConceptIds;
private Map<String, String> myConceptIdToMostRecentDate = new HashMap<String, String>();
public SctHandlerConcept(Set<String> theValidConceptIds) {
myValidConceptIds = theValidConceptIds;
@ -423,11 +429,18 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
@Override
public void accept(CSVRecord theRecord) {
String id = theRecord.get("id");
boolean active = "1".equals(theRecord.get("active"));
if (!active) {
return;
String date = theRecord.get("effectiveTime");
if (!myConceptIdToMostRecentDate.containsKey(id) || myConceptIdToMostRecentDate.get(id).compareTo(date) < 0) {
boolean active = "1".equals(theRecord.get("active"));
if (active) {
myValidConceptIds.add(id);
} else {
myValidConceptIds.remove(id);
}
myConceptIdToMostRecentDate.put(id, date);
}
myValidConceptIds.add(id);
}
}
@ -496,34 +509,29 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
if (!active) {
return;
}
TermConcept typeConcept = findConcept(myCode2concept, typeId);
TermConcept sourceConcept = findConcept(myCode2concept, sourceId);
TermConcept targetConcept = findConcept(myCode2concept, destinationId);
if (typeConcept.getDisplay().equals("Is a (attribute)")) {
if (!sourceId.equals(destinationId)) {
TermConceptParentChildLink link = new TermConceptParentChildLink();
link.setChild(sourceConcept);
link.setParent(targetConcept);
link.setRelationshipType(TermConceptParentChildLink.RelationshipTypeEnum.ISA);
link.setCodeSystem(myCodeSystemVersion);
myRootConcepts.remove(link.getChild().getCode());
TermConcept typeConcept = myCode2concept.get(typeId);
TermConcept sourceConcept = myCode2concept.get(sourceId);
TermConcept targetConcept = myCode2concept.get(destinationId);
if (sourceConcept != null && targetConcept != null && typeConcept != null) {
if (typeConcept.getDisplay().equals("Is a (attribute)")) {
if (!sourceId.equals(destinationId)) {
TermConceptParentChildLink link = new TermConceptParentChildLink();
link.setChild(sourceConcept);
link.setParent(targetConcept);
link.setRelationshipType(TermConceptParentChildLink.RelationshipTypeEnum.ISA);
link.setCodeSystem(myCodeSystemVersion);
myRootConcepts.remove(link.getChild().getCode());
targetConcept.addChild(sourceConcept, RelationshipTypeEnum.ISA);
targetConcept.addChild(sourceConcept, RelationshipTypeEnum.ISA);
}
} else if (ignoredTypes.contains(typeConcept.getDisplay())) {
// ignore
} else {
// ourLog.warn("Unknown relationship type: {}/{}", typeId, typeConcept.getDisplay());
}
} else if (ignoredTypes.contains(typeConcept.getDisplay())) {
// ignore
} else {
// ourLog.warn("Unknown relationship type: {}/{}", typeId, typeConcept.getDisplay());
}
}
private TermConcept findConcept(final Map<String, TermConcept> code2concept, String typeId) {
TermConcept typeConcept = code2concept.get(typeId);
if (typeConcept == null) {
throw new InternalErrorException("Unknown type ID: " + typeId);
}
return typeConcept;
}
}
private static class SinkOutputStream extends OutputStream {

View File

@ -857,7 +857,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
@Test
public void testDocumentManifestResources() throws Exception {
myFhirCtx.getResourceDefinition(Practitioner.class);
myFhirCtx.getResourceDefinition(ca.uhn.fhir.model.dstu.resource.DocumentManifest.class);
myFhirCtx.getResourceDefinition(DocumentManifest.class);
IGenericClient client = ourClient;

View File

@ -76,28 +76,25 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertThat(resp, containsString("</contains>"));
assertThat(resp, containsString("</expansion>"));
/*
* Filter with display name
*/
}
@Test
public void testExpandByIdWithFilter() throws IOException {
//@formatter:off
respParam = ourClient
Parameters respParam = ourClient
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.withParameter(Parameters.class, "filter", new StringType("first"))
.execute();
expanded = (ValueSet) respParam.getParameter().get(0).getResource();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
//@formatter:on
expanded = myValueSetDao.expand(myExtensionalVsId, ("systolic"));
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
//@formatter:off
assertThat(resp, stringContainsInOrder(
"<code value=\"11378-7\"/>",
"<display value=\"Systolic blood pressure at First encounter\"/>"));
//@formatter:on
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
}

View File

@ -1,12 +1,20 @@
package ca.uhn.fhir.jpa.term;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInRelativeOrder;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@ -14,22 +22,33 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
@RunWith(MockitoJUnitRunner.class)
public class TerminologyLoaderSvcTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvcTest.class);
private TerminologyLoaderSvc mySvc;
@Mock
private IHapiTerminologySvc myTermSvc;
@Captor
private ArgumentCaptor<TermCodeSystemVersion> myCsvCaptor;
@Before
public void before() {
myTermSvc = mock(IHapiTerminologySvc.class);
mySvc = new TerminologyLoaderSvc();
mySvc.setTermSvcForUnitTests(myTermSvc);
}
@ -74,6 +93,30 @@ public class TerminologyLoaderSvcTest {
RequestDetails details = mock(RequestDetails.class);
mySvc.loadSnomedCt(Collections.singletonList(bos.toByteArray()), details);
verify(myTermSvc).storeNewCodeSystemVersion(any(String.class), myCsvCaptor.capture(), any(RequestDetails.class));
TermCodeSystemVersion csv = myCsvCaptor.getValue();
TreeSet<String> allCodes = toCodes(csv);
ourLog.info(allCodes.toString());
assertThat(allCodes, containsInRelativeOrder("116680003"));
assertThat(allCodes, not(containsInRelativeOrder("207527008")));
}
private TreeSet<String> toCodes(TermCodeSystemVersion theCsv) {
TreeSet<String> retVal = new TreeSet<String>();
for (TermConcept next : theCsv.getConcepts()) {
toCodes(retVal, next);
}
return retVal;
}
private void toCodes(TreeSet<String> theCodes, TermConcept theConcept) {
theCodes.add(theConcept.getCode());
for (TermConceptParentChildLink next : theConcept.getChildren()) {
toCodes(theCodes, next.getChild());
}
}
@Test

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.term;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@ -102,6 +103,15 @@ public class TerminologySvcImplTest extends BaseJpaDstu3Test {
}
@Test
public void testReindexTerminology() {
IIdType id = createCodeSystem();
assertThat(mySystemDao.markAllResourcesForReindexing(), greaterThan(0));
assertThat(mySystemDao.performReindexingPass(100, mySrd), greaterThan(0));
}
private IIdType createCodeSystem() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);

View File

@ -13,3 +13,6 @@ id effectiveTime active moduleId definitionStatusId
126813005 20020131 1 900000000000207008 900000000000074008
126813006 20020131 1 900000000000207008 900000000000074008
126817006 20020131 1 900000000000207008 900000000000074008
207527008 20020131 1 900000000000207008 900000000000074008
207527008 20040731 1 900000000000207008 900000000000073002
207527008 20090731 0 900000000000207008 900000000000074008

View File

@ -9,3 +9,12 @@ id effectiveTime active moduleId conceptId languageCode typeId term caseSignific
108019 20020131 1 900000000000207008 126820003 en 900000000000013009 Neoplasm of abdominal esophagus 900000000000020002
110017 20020131 1 900000000000207008 126822006 en 900000000000013009 Neoplasm of middle third of esophagus 900000000000020002
181114011 20020131 1 900000000000207008 116680003 en 900000000000013009 Is a (attribute) 900000000000020002
317927012 20020131 1 900000000000207008 207527008 en 900000000000013009 [D]Old age 900000000000017005
317927012 20080731 1 900000000000207008 207527008 en 900000000000013009 [D]Old age 900000000000020002
317927012 20090731 1 900000000000207008 207527008 en 900000000000013009 [D]Old age 900000000000017005
593143017 20020131 1 900000000000207008 207527008 en 900000000000003001 [D]Old age (context-dependent category) 900000000000020002
593143017 20030731 1 900000000000207008 207527008 en 900000000000003001 [D]Old age (context-dependent category) 900000000000017005
593143017 20060731 0 900000000000207008 207527008 en 900000000000003001 [D]Old age (context-dependent category) 900000000000017005
1222549013 20020731 1 900000000000207008 207527008 en 900000000000013009 [D]Senility 900000000000020002
2608974012 20060731 1 900000000000207008 207527008 en 900000000000003001 [D]Old age (situation) 900000000000020002
2608974012 20080731 1 900000000000207008 207527008 en 900000000000003001 [D]Old age (situation) 900000000000017005

View File

@ -67,7 +67,7 @@ public class ServerProfileProvider implements IResourceProvider {
@Search()
public List<Profile> getAllProfiles(HttpServletRequest theRequest) {
final String serverBase = getServerBase(theRequest);
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitions());
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitionsWithExplicitId());
Collections.sort(defs, new Comparator<RuntimeResourceDefinition>() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {

View File

@ -953,9 +953,15 @@ public class RestfulServerMethodTest {
@Test
public void testServerProfileProviderFindsProfiles() {
ServerProfileProvider profileProvider = (ServerProfileProvider)ourRestfulServer.getServerProfilesProvider();
IdDt id = new IdDt("Profile", "observation");
Profile profile = profileProvider.getProfileById(createHttpServletRequest(), id);
assertNull(profile);
id = new IdDt("Profile", "patient");
profile = profileProvider.getProfileById(createHttpServletRequest(), id);
assertNotNull(profile);
}
@Test

View File

@ -66,7 +66,7 @@ public class ServerProfileProvider implements IResourceProvider {
@Search()
public List<StructureDefinition> getAllProfiles(HttpServletRequest theRequest) {
final String serverBase = getServerBase(theRequest);
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitions());
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitionsWithExplicitId());
Collections.sort(defs, new Comparator<RuntimeResourceDefinition>() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {

View File

@ -67,7 +67,7 @@ public class ServerProfileProvider implements IResourceProvider {
@Search()
public List<StructureDefinition> getAllProfiles(HttpServletRequest theRequest) {
final String serverBase = getServerBase(theRequest);
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitions());
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitionsWithExplicitId());
Collections.sort(defs, new Comparator<RuntimeResourceDefinition>() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {

View File

@ -0,0 +1,194 @@
package ca.uhn.fhir.context;
import static org.hamcrest.Matchers.containsInRelativeOrder;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
import java.util.TreeSet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class ContextScanningDstu3Test {
private static FhirContext ourCtx = FhirContext.forDstu3();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ContextScanningDstu3Test.class);
private static int ourPort;
private static Server ourServer;
@Test
public void testContextDoesntScanUnneccesaryTypes() {
FhirContext ctx = FhirContext.forDstu3();
TreeSet<String> resDefs = scannedResourceNames(ctx);
TreeSet<String> elementDefs = scannedElementNames(ctx);
ourLog.info("Have {} resource definitions: {}", ctx.getAllResourceDefinitions().size(), resDefs);
ourLog.info("Have {} element definitions: {}", ctx.getElementDefinitions().size(), elementDefs);
assertThat(resDefs, not(containsInRelativeOrder("Observation")));
IGenericClient client = ctx.newRestfulGenericClient("http://localhost:" + ourPort);
client.read().resource(Patient.class).withId("1").execute();
resDefs = scannedResourceNames(ctx);
elementDefs = scannedElementNames(ctx);
ourLog.info("Have {} resource definitions: {}", ctx.getAllResourceDefinitions().size(), resDefs);
ourLog.info("Have {} element definitions: {}", ctx.getElementDefinitions().size(), elementDefs);
assertThat(resDefs, not(containsInRelativeOrder("Observation")));
client.read().resource(Observation.class).withId("1").execute();
resDefs = scannedResourceNames(ctx);
elementDefs = scannedElementNames(ctx);
ourLog.info("Have {} resource definitions: {}", ctx.getAllResourceDefinitions().size(), resDefs);
ourLog.info("Have {} element definitions: {}", ctx.getElementDefinitions().size(), elementDefs);
assertThat(resDefs, containsInRelativeOrder("Observation"));
}
public static void main(String[] args) {
// 1.6 - no defer - Took 6700 ms - 6.7ms / pass
// 1.6 - no defer - Took 6127 ms - 6.127ms / pass
// 1.6 - defer - Took 2523 ms - 2.523ms / pass
// 1.6 - defer - Took 2328 ms - 2.328ms / pass
int passes = 1000;
long start = System.currentTimeMillis();
for (int i = 0; i < passes; i++) {
FhirContext ctx = FhirContext.forDstu3();
ctx.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING);
ctx.getResourceDefinition("Observation");
}
long end = System.currentTimeMillis();
long delay = end-start;
float per = (float)delay / (float)passes;
ourLog.info("Took {} ms - {}ms / pass", delay, per);
}
@Test
public void testDeferredScanning() {
FhirContext ctx = FhirContext.forDstu3();
ctx.getRestfulClientFactory().setSocketTimeout(600000);
ctx.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING);
TreeSet<String> resDefs = scannedResourceNames(ctx);
TreeSet<String> elementDefs = scannedElementNames(ctx);
ourLog.info("Have {} resource definitions: {}", ctx.getAllResourceDefinitions().size(), resDefs);
ourLog.info("Have {} element definitions: {}", ctx.getElementDefinitions().size(), elementDefs);
assertThat(resDefs, not(containsInRelativeOrder("Observation")));
BaseRuntimeElementCompositeDefinition<?> compositeDef = (BaseRuntimeElementCompositeDefinition<?>) ctx.getElementDefinition("identifier");
assertFalse(compositeDef.isSealed());
IGenericClient client = ctx.newRestfulGenericClient("http://localhost:" + ourPort);
client.read().resource(Patient.class).withId("1").execute();
resDefs = scannedResourceNames(ctx);
elementDefs = scannedElementNames(ctx);
ourLog.info("Have {} resource definitions: {}", ctx.getAllResourceDefinitions().size(), resDefs);
ourLog.info("Have {} element definitions: {}", ctx.getElementDefinitions().size(), elementDefs);
assertThat(resDefs, not(containsInRelativeOrder("Observation")));
compositeDef = (BaseRuntimeElementCompositeDefinition<?>) ctx.getElementDefinition("identifier");
assertFalse(compositeDef.isSealed());
client.read().resource(Observation.class).withId("1").execute();
resDefs = scannedResourceNames(ctx);
elementDefs = scannedElementNames(ctx);
ourLog.info("Have {} resource definitions: {}", ctx.getAllResourceDefinitions().size(), resDefs);
ourLog.info("Have {} element definitions: {}", ctx.getElementDefinitions().size(), elementDefs);
assertThat(resDefs, containsInRelativeOrder("Observation"));
compositeDef = (BaseRuntimeElementCompositeDefinition<?>) ctx.getElementDefinition("identifier");
assertTrue(compositeDef.isSealed());
}
private TreeSet<String> scannedResourceNames(FhirContext ctx) {
TreeSet<String> defs = new TreeSet<String>();
for (RuntimeResourceDefinition next : ctx.getAllResourceDefinitions()) {
defs.add(next.getName());
}
return defs;
}
private TreeSet<String> scannedElementNames(FhirContext ctx) {
TreeSet<String> defs = new TreeSet<String>();
for (BaseRuntimeElementDefinition<?> next : ctx.getElementDefinitions()) {
defs.add(next.getName());
}
return defs;
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setResourceProviders(new PatientProvider(), new ObservationProvider());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
}
public static class PatientProvider implements IResourceProvider {
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
@Read
public Patient read(@IdParam IdType theId) {
return (Patient) new Patient().setId(theId);
}
}
public static class ObservationProvider implements IResourceProvider {
@Override
public Class<Observation> getResourceType() {
return Observation.class;
}
@Read
public Observation read(@IdParam IdType theId) {
Observation retVal = new Observation();
retVal.setId(theId);
retVal.addIdentifier().setSystem("ISYS").setValue("IVAL");
retVal.setStatus(ObservationStatus.FINAL);
retVal.setValue(new StringType("VAL"));
return retVal;
}
}
}

View File

@ -67,7 +67,7 @@ public class ServerProfileProvider implements IResourceProvider {
@Search()
public List<StructureDefinition> getAllProfiles(HttpServletRequest theRequest) {
final String serverBase = getServerBase(theRequest);
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitions());
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitionsWithExplicitId());
Collections.sort(defs, new Comparator<RuntimeResourceDefinition>() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {

View File

@ -126,6 +126,16 @@ datatype.unsignedInt=org.hl7.fhir.instance.model.UnsignedIntType
datatype.uri=org.hl7.fhir.instance.model.UriType
#datatype.xhtml=org.hl7.fhir.instance.model.XhtmlType
datatype.SimpleQuantity=org.hl7.fhir.instance.model.SimpleQuantity
datatype.Age=org.hl7.fhir.instance.model.Age
datatype.Count=org.hl7.fhir.instance.model.Count
datatype.Distance=org.hl7.fhir.instance.model.Distance
datatype.Duration=org.hl7.fhir.instance.model.Duration
datatype.Money=org.hl7.fhir.instance.model.Money
datatype.xhtmlNode=org.hl7.fhir.instance.utilities.xhtml.XhtmlNode
datatype.narrative=org.hl7.fhir.instance.model.Narrative
datatype.markdown=org.hl7.fhir.instance.model.MarkdownType
datatype.Extension=org.hl7.fhir.instance.model.Extension
datatype.reference=org.hl7.fhir.instance.model.Reference
datatype.enumeration=org.hl7.fhir.instance.model.Enumeration

View File

@ -27,6 +27,7 @@ import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.tools.generic.EscapeTool;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.tinder.parser.BaseStructureSpreadsheetParser;
import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingSpreadsheet;
@ -104,6 +105,15 @@ public class TinderJpaRestServerMojo extends AbstractMojo {
baseResourceNames.add(next.substring("resource.".length()).toLowerCase());
}
}
/*
* No spreadsheet existed for Binary in DSTU1 so we don't generate it.. this
* is something we could work around, but at this point why bother since it's
* only an issue for DSTU1
*/
if (fhirContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
baseResourceNames.remove("binary");
}
}
for (int i = 0; i < baseResourceNames.size(); i++) {

View File

@ -38,6 +38,8 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Binary;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt;
import ca.uhn.fhir.tinder.TinderStructuresMojo;
import ca.uhn.fhir.tinder.ValueSetGenerator;
import ca.uhn.fhir.tinder.model.BaseElement;
@ -564,6 +566,14 @@ public abstract class BaseStructureParser {
myNameToResourceClass.put("Binary", thePackageBase + ".resource.Binary");
myNameToDatatypeClass.put("Extension", ExtensionDt.class.getName());
if (determineVersionEnum() == FhirVersionEnum.DSTU1) {
myNameToDatatypeClass.put("boundCode", BoundCodeDt.class.getName());
myNameToDatatypeClass.put("boundCodeableConcept", BoundCodeableConceptDt.class.getName());
} else if (determineVersionEnum() == FhirVersionEnum.DSTU2) {
myNameToDatatypeClass.put("boundCode", BoundCodeDt.class.getName());
myNameToDatatypeClass.put("boundCodeableConcept", ca.uhn.fhir.model.dstu2.composite.BoundCodeableConceptDt.class.getName());
}
try {
File versionFile = new File(theResourceOutputDirectory, "fhirversion.properties");
OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(versionFile, false), "UTF-8");

View File

@ -6,6 +6,7 @@ resource.AllergyIntolerance=ca.uhn.fhir.model.dstu.resource.AllergyIntolerance
resource.Appointment=ca.uhn.fhir.model.dstu.resource.Appointment
resource.AppointmentResponse=ca.uhn.fhir.model.dstu.resource.AppointmentResponse
resource.Availability=ca.uhn.fhir.model.dstu.resource.Availability
resource.Binary=ca.uhn.fhir.model.dstu.resource.Binary
resource.CarePlan=ca.uhn.fhir.model.dstu.resource.CarePlan
resource.Claim=ca.uhn.fhir.model.dstu.resource.Claim
resource.Composition=ca.uhn.fhir.model.dstu.resource.Composition
@ -114,3 +115,6 @@ datatype.time=ca.uhn.fhir.model.primitive.TimeDt
datatype.unsignedInt=ca.uhn.fhir.model.primitive.UnsignedIntDt
datatype.uri=ca.uhn.fhir.model.primitive.UriDt
datatype.xhtml=ca.uhn.fhir.model.primitive.XhtmlDt
datatype.boundCode=ca.uhn.fhir.model.primitive.BoundCodeDt
datatype.boundCodeableConcept=ca.uhn.fhir.model.primitive.BoundCodeableConceptDt

View File

@ -160,3 +160,6 @@ datatype.time=ca.uhn.fhir.model.primitive.TimeDt
datatype.unsignedInt=ca.uhn.fhir.model.primitive.UnsignedIntDt
datatype.uri=ca.uhn.fhir.model.primitive.UriDt
datatype.xhtml=ca.uhn.fhir.model.primitive.XhtmlDt
datatype.boundCode=ca.uhn.fhir.model.primitive.BoundCodeDt
datatype.boundCodeableConcept=ca.uhn.fhir.model.dstu2.composite.BoundCodeableConceptDt

View File

@ -7,6 +7,29 @@
</properties>
<body>
<release version="1.6" date="TBD">
<action type="fix">
Performance has been improved for the initial FhirContext
object creation by avoiding a lot of unnecessary reflection. HAPI FHIR
1.5 had a regression compared to previous releases
and this has been corrected, but other improvements have been
made so that this release is faster than previous releases too.
<![CDATA[<br/><br/>]]>
In addition, a new "deferred scan" mode has been implemented for
even faster initialization on slower environments (e.g. Android).
See the <![CDATA[<a href="./doc_rest_client_http_config.html#performance">performance documentation</a>]]>
for more information.
<![CDATA[<br/><br/>]]>
The following shows our benchmarks for context initialization across several
versions of HAPI:
<![CDATA[
<ul>
<li>Version 1.4: <b>560ms</b></li>
<li>Version 1.5: <b>800ms</b></li>
<li>Version 1.6: <b>340ms</b></li>
<li>Version 1.6 (deferred mode): <b>240ms</b></li>
</ul>
]]>
</action>
<action type="add">
Bump the version of a few dependencies to the
latest versions (dependent HAPI modules listed in brackets):

View File

@ -90,7 +90,7 @@
<item name="Fluent/Generic Client" href="./doc_rest_client.html" />
<item name="Annotation Client" href="./doc_rest_client_annotation.html" />
<item name="Interceptors (client)" href="./doc_rest_client_interceptor.html"/>
<item name="Client HTTP Configuration" href="./doc_rest_client_http_config.html"/>
<item name="Client Configuration" href="./doc_rest_client_http_config.html"/>
<item name="Client Examples" href="./doc_rest_client_examples.html"/>
<item name="JAX-RS Client &amp; Alternate HTTP Providers" href="./doc_rest_client_alternate_provider.html"/>
</item>

View File

@ -59,6 +59,16 @@
</subsection>
</section>
<section name="Performance">
<p>
On mobile devices, performance problems are particularly noticeable. This
is made worse by the fact that some Android devices have much slower performance
than modern desktop computers. See the
<a href="./doc_rest_client_http_config.html#performance">Client Configuration Performance</a>
page for some tips on how to improve client performance.
</p>
</section>
</body>
</document>

View File

@ -3,11 +3,75 @@
xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
<properties>
<title>Client HTTP Configuration</title>
<title>Client Configuration</title>
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
</properties>
<body>
<section name="Configuring Client Behaviour">
<p>
This page outlines ways that the client can be configured
for specific behaviour.
</p>
</section>
<a name="performance"/>
<section name="Performance">
<subsection name="Server Conformance Check">
<p>
By default, the client will query the server before the very first
operation to download the server's conformance/metadata statement
and verify that the server is appropriate for the given client.
This check is only done once per server endpoint for a given
FhirContext.
</p>
<p>
This check is useful to prevent bugs or unexpected behaviour
when talking to servers. It may introduce unneccesary overhead
however in circumstances where the client and server are known
to be compatible. The following example shows how to disable this
check.
</p>
<macro name="snippet">
<param name="id" value="dontValidate" />
<param name="file" value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
<subsection name="Deferred Model Scanning">
<p>
By default, HAPI will scan each model type it encounters
as soon as it encounters it. This scan includes a check for
all fields within the type, and makes use of reflection to do this.
</p>
<p>
While this process is not particularly significant on reasonably
performant machines (current benchmarks show that this takes
roughly 6 seconds on one developer workstation), on some devices
(e.g. Android phones where every millisecond counts)
it may be desirable to defer this scan.
</p>
<p>
When the scan is deferred, objects will only be scanned when they
are actually accessed, meaning that only types that are
actually used in an application get scanned.
</p>
<p>
The following example shows how to defer model scanning:
</p>
<macro name="snippet">
<param name="id" value="deferModelScanning" />
<param name="file" value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
</section>
<section name="Configuring the HTTP Client">

View File

@ -35,7 +35,7 @@
<li><a href="./doc_rest_client.html">Fluent/Generic Client</a></li>
<li><a href="./doc_rest_client_annotation.html">Annotation Client</a></li>
<li><a href="./doc_rest_client_interceptor.html">Interceptors (client)</a></li>
<li><a href="./doc_rest_client_http_config.html">Client HTTP Configuration</a></li>
<li><a href="./doc_rest_client_http_config.html">Client Configuration</a></li>
<li><a href="./doc_rest_client_examples.html">Client Examples</a></li>
<li><a href="./doc_rest_client_alternate_provider.html">JAX-RS Client &amp; Alternate HTTP Providers</a></li>
</ul>