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 theImplementingClass, boolean theStandardType) { + private volatile boolean mySealed; + + public BaseRuntimeElementCompositeDefinition(String theName, Class 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 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 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) current.getSuperclass(); + } else { + current = null; + } + } while (current != null); + + for (Class 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 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 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 et = (Class) 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 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) 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 blockDef = (Class) 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 nextDatatype = (Class) nextElementType; + + BaseRuntimeChildDatatypeDefinition def; + if (IPrimitiveType.class.isAssignableFrom(nextElementType)) { + if (nextElementType.equals(BoundCodeDt.class)) { + IValueSetEnumBinder> binder = ModelScanner.getBoundCodeBinder(next); + Class> enumType = ModelScanner.determineEnumTypeForBoundField(next); + def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType); + } else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) { + Class> 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> 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 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 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 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 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 getDefaultTypeForProfile(String theProfile) { + validateInitialized(); return myDefaultTypeForProfile.get(theProfile); } @@ -206,6 +219,7 @@ public class FhirContext { */ @SuppressWarnings("unchecked") public BaseRuntimeElementDefinition getElementDefinition(Class theElementType) { + validateInitialized(); BaseRuntimeElementDefinition retVal = myClassToElementDefinition.get(theElementType); if (retVal == null) { retVal = scanDatatype((Class) 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 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 theClass, Object theBlockDef) { + // TODO remove } private void scanCompositeDatatype(Class 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 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 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) current.getSuperclass(); - } else { - current = null; - } - } while (current != null); - - for (Class 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 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 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 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) nextElementType); - } - - } else if (extensionAttr != null) { - /* - * Child is an extension - */ - Class et = (Class) 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) nextElementType); - } - } else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) || IBaseReference.class.isAssignableFrom(nextElementType)) { - /* - * Child is a resource reference - */ - List> refTypesList = new ArrayList>(); - for (Class 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) 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 blockDef = (Class) 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 nextDatatype = (Class) nextElementType; - - addScanAlso(nextDatatype); - BaseRuntimeChildDatatypeDefinition def; - if (IPrimitiveType.class.isAssignableFrom(nextElementType)) { - if (nextElementType.equals(BoundCodeDt.class)) { - IValueSetEnumBinder> binder = getBoundCodeBinder(next); - Class> enumType = determineEnumTypeForBoundField(next); - def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType); - } else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) { - Class> 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> 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> determineEnumTypeForBoundField(Field next) { + static Class> determineEnumTypeForBoundField(Field next) { @SuppressWarnings("unchecked") Class> enumType = (Class>) 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 clazz = (Class) 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 myResourceBlockType; + private FhirContext myContext; - public RuntimeChildResourceBlockDefinition(Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName, Class theResourceBlockType) throws ConfigurationException { + public RuntimeChildResourceBlockDefinition(FhirContext theContext, Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName, Class 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 theDatatype) { if (myResourceBlockType.equals(theDatatype)) { @@ -60,7 +66,7 @@ public class RuntimeChildResourceBlockDefinition extends BaseRuntimeDeclaredChil @Override public BaseRuntimeElementDefinition getChildElementDefinitionByDatatype(Class 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 myProfileOfType; private BaseRuntimeElementDefinition myProfileOf; - public RuntimeCompositeDatatypeDefinition(DatatypeDef theDef, Class theImplementingClass, boolean theStandardType) { - super(theDef.name(), theImplementingClass, theStandardType); + public RuntimeCompositeDatatypeDefinition(DatatypeDef theDef, Class 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 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 theImplementingClass, boolean theStandardType) { - super(theDef, theImplementingClass, theStandardType); + public RuntimeExtensionDtDefinition(DatatypeDef theDef, Class 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 theImplementingClass, boolean theStandardType) { - super(theName, theImplementingClass, theStandardType); + public RuntimeResourceBlockDefinition(String theName, Class 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 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 theClass, ResourceDef theResourceAnnotation, boolean theStandardType) { - super(theResourceName, theClass, theStandardType); + public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class 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) target; do { target = target.getSuperclass(); if (IBaseResource.class.isAssignableFrom(target) && target.getAnnotation(ResourceDef.class) != null) { - myBaseDefinition = (RuntimeResourceDefinition) theClassToElementDefinitions.get(target); + myBaseType = (Class) 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: +

    + + + + + +
    + +
    diff --git a/src/site/xdoc/docindex.xml b/src/site/xdoc/docindex.xml index 14f35bd4faa..dd3929f6338 100644 --- a/src/site/xdoc/docindex.xml +++ b/src/site/xdoc/docindex.xml @@ -35,7 +35,7 @@
  • Fluent/Generic Client
  • Annotation Client
  • Interceptors (client)
  • -
  • Client HTTP Configuration
  • +
  • Client Configuration
  • Client Examples
  • JAX-RS Client & Alternate HTTP Providers