diff --git a/examples/src/main/java/example/GenericClientExample.java b/examples/src/main/java/example/GenericClientExample.java
index a193d940ef8..ca24368ecc9 100644
--- a/examples/src/main/java/example/GenericClientExample.java
+++ b/examples/src/main/java/example/GenericClientExample.java
@@ -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,13 +26,39 @@ 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
// We're connecting to a DSTU1 compliant server in this example
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDatatypeDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDatatypeDefinition.java
index 809fda005dd..35abc30255f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDatatypeDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDatatypeDefinition.java
@@ -97,6 +97,9 @@ public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDecl
@Override
void sealAndInitialize(FhirContext theContext, Map, BaseRuntimeElementDefinition>> theClassToElementDefinitions) {
myElementDefinition = theClassToElementDefinitions.get(getDatatype());
+ if (myElementDefinition == null) {
+ myElementDefinition = theContext.getElementDefinition(getDatatype());
+ }
assert myElementDefinition != null : "Unknown type: " + getDatatype();
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementCompositeDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementCompositeDefinition.java
index 53493b98beb..e8d406a2a40 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementCompositeDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementCompositeDefinition.java
@@ -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 extends BaseRuntimeElementDefinition {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeElementCompositeDefinition.class);
private List myChildren = new ArrayList();
private List myChildrenAndExtensions;
+ private Map, BaseRuntimeElementDefinition>> myClassToElementDefinitions;
+ private FhirContext myContext;
private Map myNameToChild = new HashMap();
- 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, BaseRuntimeElementDefinition>> theClassToElementDefinitions) {
super(theName, theImplementingClass, theStandardType);
+
+ myContext = theContext;
+ myClassToElementDefinitions = theClassToElementDefinitions;
}
void addChild(BaseRuntimeChildDefinition theNext) {
@@ -53,11 +97,13 @@ public abstract class BaseRuntimeElementCompositeDefinition 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(myNameToChild.keySet()));
@@ -66,15 +112,354 @@ public abstract class BaseRuntimeElementCompositeDefinition ext
}
public List getChildren() {
+ validateSealed();
return myChildren;
}
public List 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 elementNames = new HashSet();
+ TreeMap orderToElementDef = new TreeMap();
+ TreeMap orderToExtensionDef = new TreeMap();
+
+ LinkedList> classes = new LinkedList>();
+
+ /*
+ * We scan classes for annotated fields in the class but also all of its superclasses
+ */
+ Class extends IBase> current = theClass;
+ Map forcedOrder = null;
+ do {
+ if (forcedOrder == null) {
+ ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
+ if (childOrder != null) {
+ forcedOrder = new HashMap();
+ 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 newOrderToExtensionDef = new TreeMap();
+ 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 orders = new TreeSet();
+ 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 elementNames, TreeMap theOrderToElementDef,
+ TreeMap 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 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 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 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> choiceTypes = new ArrayList>();
+ 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> refTypesList = new ArrayList>();
+ 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> 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> 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, 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 ext
myChildrenAndExtensions=Collections.unmodifiableList(children);
}
+
+ @Override
+ protected void validateSealed() {
+ if (!mySealed) {
+ synchronized(myContext) {
+ sealAndInitialize(myContext, myClassToElementDefinitions);
+ }
+ }
+ }
+
private static int findIndex(List theChildren, String theName, boolean theDefaultAtEnd) {
int index = theDefaultAtEnd ? theChildren.size() : -1;
for (ListIterator iter = theChildren.listIterator(); iter.hasNext(); ) {
@@ -152,4 +547,5 @@ public abstract class BaseRuntimeElementCompositeDefinition ext
return index;
}
+
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java
index 21c0a0808c9..523b31a89f1 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java
@@ -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 {
private static final Class VOID_CLASS = Void.class;
- private final String myName;
- private final Class extends T> myImplementingClass;
+ private Map, Constructor> myConstructors = Collections.synchronizedMap(new HashMap, Constructor>());
private List myExtensions = new ArrayList();
- private Map myUrlToExtension = new HashMap();
private List myExtensionsModifier = new ArrayList();
private List myExtensionsNonModifier = new ArrayList();
+ private final Class extends T> myImplementingClass;
+ private final String myName;
private final boolean myStandardType;
- private Map, Constructor> myConstructors = Collections.synchronizedMap(new HashMap, Constructor>());
+ private Map myUrlToExtension = new HashMap();
public BaseRuntimeElementDefinition(String theName, Class extends T> theImplementingClass, boolean theStandardType) {
assert StringUtils.isNotBlank(theName);
@@ -60,15 +59,6 @@ public abstract class BaseRuntimeElementDefinition {
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 {
myExtensions.add(theExtension);
}
- public List getExtensions() {
- return myExtensions;
- }
-
- public List getExtensionsModifier() {
- return myExtensionsModifier;
- }
-
- public List 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 getConstructor(Object theArgument) {
@@ -160,10 +101,61 @@ public abstract class BaseRuntimeElementDefinition {
return retVal;
}
+ /**
+ * @return Returns null if none
+ */
+ public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(String theExtensionUrl) {
+ validateSealed();
+ return myUrlToExtension.get(theExtensionUrl);
+ }
+
+ public List getExtensions() {
+ validateSealed();
+ return myExtensions;
+ }
+
+ public List getExtensionsModifier() {
+ validateSealed();
+ return myExtensionsModifier;
+ }
+
+ public List 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 {
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,
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
index aba5c4c1650..ce88ac26de4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
@@ -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> myCustomTypes;
private Map> myDefaultTypeForProfile = new HashMap>();
private volatile Map myIdToResourceDefinition = Collections.emptyMap();
+ private boolean myInitialized;
private HapiLocalizer myLocalizer = new HapiLocalizer();
private volatile Map> myNameToElementDefinition = Collections.emptyMap();
private volatile Map myNameToResourceDefinition = Collections.emptyMap();
private volatile Map> myNameToResourceType;
private volatile INarrativeGenerator myNarrativeGenerator;
private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
+ private Set myPerformanceOptions = new HashSet();
+ private Collection> myResourceTypesToScan;
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private final IFhirVersion myVersion;
private Map>> myVersionToNameToResourceType = Collections.emptyMap();
+ private boolean myInitializing;
/**
* @deprecated It is recommended that you use one of the static initializer methods instead
@@ -165,8 +172,8 @@ public class FhirContext {
} else {
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 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 {
*
*/
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> getElementDefinitions() {
+ validateInitialized();
return Collections.unmodifiableCollection(myClassToElementDefinition.values());
}
@@ -251,12 +268,20 @@ public class FhirContext {
return myNarrativeGenerator;
}
+ /**
+ * Get the configured performance options
+ */
+ public Set 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 getResourceDefinitions() {
+ public Collection 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,8 +570,9 @@ public class FhirContext {
return (RuntimeResourceDefinition) defs.get(theResourceType);
}
- private Map, BaseRuntimeElementDefinition>> scanResourceTypes(Collection> theResourceTypes) {
-
+ private synchronized Map, BaseRuntimeElementDefinition>> scanResourceTypes(Collection> theResourceTypes) {
+ myInitializing = true;
+
List> typesToScan = new ArrayList>();
if (theResourceTypes != null) {
typesToScan.addAll(theResourceTypes);
@@ -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 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 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}
*/
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java
index bdae5876fe9..e3a8d64176c 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java
@@ -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> getBoundCodeBinder(Field theNext) {
+ static IValueSetEnumBinder> 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 pullAnnotation(AnnotatedElement theTarget, Class theAnnotationType) {
+ static T pullAnnotation(AnnotatedElement theTarget, Class 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 elementNames = new HashSet();
- TreeMap orderToElementDef = new TreeMap();
- TreeMap orderToExtensionDef = new TreeMap();
- LinkedList> classes = new LinkedList>();
- /*
- * We scan classes for annotated fields in the class but also all of its superclasses
- */
- Class extends IBase> current = theClass;
- Map forcedOrder = null;
- do {
- if (forcedOrder == null) {
- ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
- if (childOrder != null) {
- forcedOrder = new HashMap();
- 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 newOrderToExtensionDef = new TreeMap();
- 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 orders = new TreeSet();
- 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 elementNames, TreeMap theOrderToElementDef,
- TreeMap 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 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 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 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> choiceTypes = new ArrayList>();
- 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> refTypesList = new ArrayList>();
- 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> 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> 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 clazz = (Class) 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) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/PerformanceOptionsEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/PerformanceOptionsEnum.java
new file mode 100644
index 00000000000..ceb2cf8a5ab
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/PerformanceOptionsEnum.java
@@ -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.
+ *
+ * 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.
+ *
+ */
+ DEFERRED_MODEL_SCANNING
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java
index 9d89d339d69..66f6b8aefd6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java
@@ -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();
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)) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceBlockDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceBlockDefinition.java
index bc88511d0ac..d37978f2275 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceBlockDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceBlockDefinition.java
@@ -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, BaseRuntimeElementDefinition>> theClassToElementDefinitions) {
- myElementDef = (RuntimeResourceBlockDefinition) theClassToElementDefinitions.get(myResourceBlockType);
+// myElementDef = (RuntimeResourceBlockDefinition) theClassToElementDefinitions.get(myResourceBlockType);
}
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeCompositeDatatypeDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeCompositeDatatypeDefinition.java
index fd15fc9536f..a453d93f16c 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeCompositeDatatypeDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeCompositeDatatypeDefinition.java
@@ -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, 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;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeExtensionDtDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeExtensionDtDefinition.java
index 28973fe305d..b6887e32bf6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeExtensionDtDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeExtensionDtDefinition.java
@@ -34,8 +34,8 @@ public class RuntimeExtensionDtDefinition extends RuntimeCompositeDatatypeDefini
private List 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, BaseRuntimeElementDefinition>> theClassToElementDefinitions) {
+ super(theDef, theImplementingClass, theStandardType, theContext, theClassToElementDefinitions);
}
@Override
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceBlockDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceBlockDefinition.java
index 0953f1d9e4b..b97be75ac7c 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceBlockDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceBlockDefinition.java
@@ -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 {
- 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, BaseRuntimeElementDefinition>> theClassToElementDefinitions) {
+ super(theName, theImplementingClass, theStandardType, theContext, theClassToElementDefinitions);
}
@Override
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java
index cf10276c7a6..e79dfe4ba08 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java
@@ -36,7 +36,7 @@ import ca.uhn.fhir.util.UrlUtil;
public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition {
- private RuntimeResourceDefinition myBaseDefinition;
+ private Class extends IBaseResource> myBaseType;
private Map> myCompartmentNameToSearchParams;
private FhirContext myContext;
private String myId;
@@ -45,9 +45,10 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
private String myResourceProfile;
private List 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, 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
*
*/
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 getSearchParams() {
+ validateSealed();
return mySearchParams;
}
@@ -139,6 +147,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
* Will not return null
*/
public List getSearchParamsForCompartmentName(String theCompartmentName) {
+ validateSealed();
List 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, 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;
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IRestfulClientFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IRestfulClientFactory.java
index c2fc055be9b..9ce557456e1 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IRestfulClientFactory.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IRestfulClientFactory.java
@@ -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.
*
+ * This check is primarily to validate that the server supports an appropriate
+ * version of FHIR
+ *
+ *
* The default value for this setting is defined by {@link #DEFAULT_SERVER_VALIDATION_MODE}
*
*
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
index fd862c376d6..6a451122eb1 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
@@ -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;
@@ -64,10 +68,13 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao extends BaseHapiFhirDao() {
@SuppressWarnings("unchecked")
@Override
@@ -106,8 +119,7 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao extends BaseHapiFhirDao extends BaseHapiFhirDao() {
+ @Override
+ public Integer doInTransaction(TransactionStatus theStatus) {
+
+ int maxResult = 10000;
+ Page 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 extends BaseHapiFhirDao {
@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 findResourcesRequiringReindexing(Pageable thePageRequest);
+
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java
index 785da258db3..a8d4c040c6a 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java
@@ -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
@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) {
@@ -134,12 +134,36 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3
}
@Override
- public ValueSet expand(ValueSet source, String theFilter) {
- ValueSet retVal = doExpand(source);
+ public ValueSet expand(ValueSet source, String theFilter) {
+ 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 theValueSetIdentifier, IIdType theId, IPrimitiveType theCode,
IPrimitiveType theSystem, IPrimitiveType theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java
index 40944e933b0..276596e477d 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java
@@ -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 {
@@ -78,11 +81,17 @@ public class TermConcept implements Serializable {
@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),
})
private String myCode;
-
+
@ManyToOne()
@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 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 theParentPids) {
StringBuilder b = new StringBuilder();
for (Long next : theParentPids) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java
index 1eddf7b70fe..c242f24c482 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java
@@ -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
//@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
//@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() {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvc.java
index 327e6917b61..6349c4af9bc 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvc.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvc.java
@@ -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()) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java
index 0233d8ca42b..c338118269f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java
@@ -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 theFilenameToFile, String fileNamePart, IRecordHandler handler, char theDelimiter, QuoteMode theQuoteMode) {
boolean found = false;
- for (Entry nextEntry : theFilenameToFile.entrySet()) {
+ for (Entry nextEntry : new HashMap(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 myValidConceptIds;
+ private Map myConceptIdToMostRecentDate = new HashMap();
public SctHandlerConcept(Set 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 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 {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java
index a899596c9a5..2395dcc9d39 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java
@@ -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;
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java
index f18e8553802..1e7ab182bdf 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java
@@ -76,29 +76,26 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertThat(resp, containsString(""));
assertThat(resp, containsString(""));
- /*
- * 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(
- "",
- ""));
- //@formatter:on
-
+ assertThat(resp, containsString(""));
+ assertThat(resp, not(containsString("")));
+
}
@Test
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcTest.java
index 6adb8715fd1..668c10c185d 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcTest.java
@@ -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 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 allCodes = toCodes(csv);
+ ourLog.info(allCodes.toString());
+
+ assertThat(allCodes, containsInRelativeOrder("116680003"));
+ assertThat(allCodes, not(containsInRelativeOrder("207527008")));
+ }
+
+ private TreeSet toCodes(TermCodeSystemVersion theCsv) {
+ TreeSet retVal = new TreeSet();
+ for (TermConcept next : theCsv.getConcepts()) {
+ toCodes(retVal, next);
+ }
+ return retVal;
+ }
+
+ private void toCodes(TreeSet theCodes, TermConcept theConcept) {
+ theCodes.add(theConcept.getCode());
+ for (TermConceptParentChildLink next : theConcept.getChildren()) {
+ toCodes(theCodes, next.getChild());
+ }
}
@Test
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java
index 61099941aa9..26a905b98c4 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java
@@ -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;
@@ -101,6 +102,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();
diff --git a/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Concept_Full_INT_20160131.txt b/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Concept_Full_INT_20160131.txt
index c3af8e530f3..83d9d806fc6 100644
--- a/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Concept_Full_INT_20160131.txt
+++ b/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Concept_Full_INT_20160131.txt
@@ -12,4 +12,7 @@ id effectiveTime active moduleId definitionStatusId
126815003 20020131 1 900000000000207008 900000000000074008
126813005 20020131 1 900000000000207008 900000000000074008
126813006 20020131 1 900000000000207008 900000000000074008
-126817006 20020131 1 900000000000207008 900000000000074008
\ No newline at end of file
+126817006 20020131 1 900000000000207008 900000000000074008
+207527008 20020131 1 900000000000207008 900000000000074008
+207527008 20040731 1 900000000000207008 900000000000073002
+207527008 20090731 0 900000000000207008 900000000000074008
diff --git a/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Description_Full-en_INT_20160131.txt b/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Description_Full-en_INT_20160131.txt
index cab64919839..71a4f160f92 100644
--- a/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Description_Full-en_INT_20160131.txt
+++ b/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Description_Full-en_INT_20160131.txt
@@ -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
diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerProfileProvider.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerProfileProvider.java
index f95e63f8ffd..b1fe5c04675 100644
--- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerProfileProvider.java
+++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerProfileProvider.java
@@ -67,7 +67,7 @@ public class ServerProfileProvider implements IResourceProvider {
@Search()
public List getAllProfiles(HttpServletRequest theRequest) {
final String serverBase = getServerBase(theRequest);
- List defs = new ArrayList(myContext.getResourceDefinitions());
+ List defs = new ArrayList(myContext.getResourceDefinitionsWithExplicitId());
Collections.sort(defs, new Comparator() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/RestfulServerMethodTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/RestfulServerMethodTest.java
index 8710bd1830b..f23a05bdd77 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/RestfulServerMethodTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/RestfulServerMethodTest.java
@@ -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
diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerProfileProvider.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerProfileProvider.java
index 2dd39dacdcc..fc8a0d668d1 100644
--- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerProfileProvider.java
+++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerProfileProvider.java
@@ -66,7 +66,7 @@ public class ServerProfileProvider implements IResourceProvider {
@Search()
public List getAllProfiles(HttpServletRequest theRequest) {
final String serverBase = getServerBase(theRequest);
- List defs = new ArrayList(myContext.getResourceDefinitions());
+ List defs = new ArrayList(myContext.getResourceDefinitionsWithExplicitId());
Collections.sort(defs, new Comparator() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerProfileProvider.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerProfileProvider.java
index 3a2342cce8e..2af584933f9 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerProfileProvider.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerProfileProvider.java
@@ -67,7 +67,7 @@ public class ServerProfileProvider implements IResourceProvider {
@Search()
public List getAllProfiles(HttpServletRequest theRequest) {
final String serverBase = getServerBase(theRequest);
- List defs = new ArrayList(myContext.getResourceDefinitions());
+ List defs = new ArrayList(myContext.getResourceDefinitionsWithExplicitId());
Collections.sort(defs, new Comparator() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ContextScanningDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ContextScanningDstu3Test.java
new file mode 100644
index 00000000000..d9bd38c125f
--- /dev/null
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ContextScanningDstu3Test.java
@@ -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 resDefs = scannedResourceNames(ctx);
+ TreeSet 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 resDefs = scannedResourceNames(ctx);
+ TreeSet 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 scannedResourceNames(FhirContext ctx) {
+ TreeSet defs = new TreeSet();
+ for (RuntimeResourceDefinition next : ctx.getAllResourceDefinitions()) {
+ defs.add(next.getName());
+ }
+ return defs;
+ }
+
+ private TreeSet scannedElementNames(FhirContext ctx) {
+ TreeSet defs = new TreeSet();
+ 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 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 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;
+ }
+
+ }
+
+}
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerProfileProvider.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerProfileProvider.java
index e9f226061a6..330a34b1969 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerProfileProvider.java
+++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerProfileProvider.java
@@ -67,7 +67,7 @@ public class ServerProfileProvider implements IResourceProvider {
@Search()
public List getAllProfiles(HttpServletRequest theRequest) {
final String serverBase = getServerBase(theRequest);
- List defs = new ArrayList(myContext.getResourceDefinitions());
+ List defs = new ArrayList(myContext.getResourceDefinitionsWithExplicitId());
Collections.sort(defs, new Comparator() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/resources/org/hl7/fhir/instance/model/fhirversion.properties b/hapi-fhir-structures-hl7org-dstu2/src/main/resources/org/hl7/fhir/instance/model/fhirversion.properties
index cb0d5b45aa4..f80c4af6620 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/main/resources/org/hl7/fhir/instance/model/fhirversion.properties
+++ b/hapi-fhir-structures-hl7org-dstu2/src/main/resources/org/hl7/fhir/instance/model/fhirversion.properties
@@ -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
\ No newline at end of file
diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java
index d3d930c2e18..d5f160b6ff1 100644
--- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java
+++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java
@@ -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++) {
diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java
index fc8aa218bdb..9e67ca73bdf 100644
--- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java
+++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java
@@ -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");
diff --git a/hapi-tinder-plugin/src/main/resources/ca/uhn/fhir/model/dstu/fhirversion.properties b/hapi-tinder-plugin/src/main/resources/ca/uhn/fhir/model/dstu/fhirversion.properties
index a38fb447505..e1e13cebaf1 100644
--- a/hapi-tinder-plugin/src/main/resources/ca/uhn/fhir/model/dstu/fhirversion.properties
+++ b/hapi-tinder-plugin/src/main/resources/ca/uhn/fhir/model/dstu/fhirversion.properties
@@ -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
diff --git a/hapi-tinder-plugin/src/main/resources/ca/uhn/fhir/model/dstu2/fhirversion.properties b/hapi-tinder-plugin/src/main/resources/ca/uhn/fhir/model/dstu2/fhirversion.properties
index f26b3e1f4de..d8acab8da9c 100644
--- a/hapi-tinder-plugin/src/main/resources/ca/uhn/fhir/model/dstu2/fhirversion.properties
+++ b/hapi-tinder-plugin/src/main/resources/ca/uhn/fhir/model/dstu2/fhirversion.properties
@@ -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
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index b79b82cfc6f..f6641ddd374 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -7,6 +7,29 @@
+
+ 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.
+
]]>
+ In addition, a new "deferred scan" mode has been implemented for
+ even faster initialization on slower environments (e.g. Android).
+ See the performance documentation]]>
+ for more information.
+
]]>
+ The following shows our benchmarks for context initialization across several
+ versions of HAPI:
+
+ Version 1.4: 560ms
+ Version 1.5: 800ms
+ Version 1.6: 340ms
+ Version 1.6 (deferred mode): 240ms
+
+ ]]>
+
Bump the version of a few dependencies to the
latest versions (dependent HAPI modules listed in brackets):
diff --git a/src/site/site.xml b/src/site/site.xml
index 8ca6e0b98b1..d008088cdac 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -90,7 +90,7 @@
-
+
diff --git a/src/site/xdoc/doc_android.xml.vm b/src/site/xdoc/doc_android.xml.vm
index 4cad4088de0..b1db0b105fe 100644
--- a/src/site/xdoc/doc_android.xml.vm
+++ b/src/site/xdoc/doc_android.xml.vm
@@ -59,6 +59,16 @@
+
+
+ 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
+ Client Configuration Performance
+ page for some tips on how to improve client performance.
+
+
+
diff --git a/src/site/xdoc/doc_rest_client_http_config.xml b/src/site/xdoc/doc_rest_client_http_config.xml
index cff02c8195f..10247b25523 100644
--- a/src/site/xdoc/doc_rest_client_http_config.xml
+++ b/src/site/xdoc/doc_rest_client_http_config.xml
@@ -3,11 +3,75 @@
xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
- Client HTTP Configuration
+ Client Configuration
James Agnew
+
+
+
+ This page outlines ways that the client can be configured
+ for specific behaviour.
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+ 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.
+
+
+ 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.
+
+
+ The following example shows how to defer model scanning:
+
+
+
+
+
+
+
+
+