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 af51741bc5d..0ee3f9ae573 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 @@ -70,6 +70,8 @@ import ca.uhn.fhir.model.api.IResourceBlock; import ca.uhn.fhir.model.api.IValueSetEnumBinder; import ca.uhn.fhir.model.api.annotation.Block; import ca.uhn.fhir.model.api.annotation.Child; +import ca.uhn.fhir.model.api.annotation.Compartment; +import ca.uhn.fhir.model.api.annotation.Compartments; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Extension; @@ -87,7 +89,6 @@ import ca.uhn.fhir.util.ReflectionUtil; class ModelScanner { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class); - private final Map, Class> myAnnotationForwards = new HashMap, Class>(); private Map, BaseRuntimeElementDefinition> myClassToElementDefinitions = new HashMap, BaseRuntimeElementDefinition>(); private FhirContext myContext; private Map myIdToResourceDefinition = new HashMap(); @@ -101,7 +102,8 @@ class ModelScanner { private Set> myVersionTypes; - ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map, BaseRuntimeElementDefinition> theExistingDefinitions, Collection> theResourceTypes) throws ConfigurationException { + ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map, BaseRuntimeElementDefinition> theExistingDefinitions, + Collection> theResourceTypes) throws ConfigurationException { myContext = theContext; myVersion = theVersion; Set> toScan; @@ -189,7 +191,7 @@ class ModelScanner { Map> resourceTypes = myNameToResourceType; myVersionTypes = scanVersionPropertyFile(theDatatypes, resourceTypes, myVersion); - + // toScan.add(DateDt.class); // toScan.add(CodeDt.class); // toScan.add(DecimalDt.class); @@ -233,68 +235,13 @@ class ModelScanner { } /** - * There are two implementations of all of the annotations (e.g. {@link Child} and - * {@link org.hl7.fhir.instance.model.annotations.Child}) since the HL7.org ones will eventually replace the HAPI - * 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. + * There are two implementations of all of the annotations (e.g. {@link Child} and {@link org.hl7.fhir.instance.model.annotations.Child}) since the HL7.org ones will eventually replace the HAPI + * 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. */ - @SuppressWarnings("unchecked") - private T pullAnnotation(Class theContainer, AnnotatedElement theTarget, Class theAnnotationType) { - + private T pullAnnotation(AnnotatedElement theTarget, Class theAnnotationType) { T retVal = theTarget.getAnnotation(theAnnotationType); -// if (myContext.getVersion().getVersion() != FhirVersionEnum.DSTU2_HL7ORG) { - return retVal; -// } -// -// if (retVal == null) { -// final Class altAnnotationClass; -// /* -// * Use a cache to minimize Class.forName calls, since they are slow and expensive.. -// */ -// if (myAnnotationForwards.containsKey(theAnnotationType) == false) { -// String sourceClassName = theAnnotationType.getName(); -// String candidateAltClassName = sourceClassName.replace("ca.uhn.fhir.model.api.annotation", "org.hl7.fhir.instance.model.annotations"); -// if (!sourceClassName.equals(candidateAltClassName)) { -// Class forName; -// try { -// forName = Class.forName(candidateAltClassName); -// ourLog.debug("Forwarding annotation request for [{}] to class [{}]", theAnnotationType, forName); -// } catch (ClassNotFoundException e) { -// forName = null; -// } -// altAnnotationClass = (Class) forName; -// } else { -// altAnnotationClass = null; -// } -// myAnnotationForwards.put(theAnnotationType, altAnnotationClass); -// } else { -// altAnnotationClass = myAnnotationForwards.get(theAnnotationType); -// } -// -// if (altAnnotationClass == null) { -// return null; -// } -// -// final Annotation altAnnotation; -// altAnnotation = theTarget.getAnnotation(altAnnotationClass); -// if (altAnnotation == null) { -// return null; -// } -// -// InvocationHandler h = new InvocationHandler() { -// -// @Override -// public Object invoke(Object theProxy, Method theMethod, Object[] theArgs) throws Throwable { -// Method altMethod = altAnnotationClass.getMethod(theMethod.getName(), theMethod.getParameterTypes()); -// return altMethod.invoke(altAnnotation, theArgs); -// } -// }; -// retVal = (T) Proxy.newProxyInstance(theAnnotationType.getClassLoader(), new Class[] { theAnnotationType }, h); -// -// } -// -// return retVal; + return retVal; } private void scan(Class theClass) throws ConfigurationException { @@ -303,17 +250,18 @@ class ModelScanner { return; } - ResourceDef resourceDefinition = pullAnnotation(theClass, theClass, ResourceDef.class); + ResourceDef resourceDefinition = pullAnnotation(theClass, ResourceDef.class); if (resourceDefinition != null) { if (!IBaseResource.class.isAssignableFrom(theClass)) { - throw new ConfigurationException("Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName()); + throw new ConfigurationException( + "Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName()); } @SuppressWarnings("unchecked") Class resClass = (Class) theClass; scanResource(resClass, resourceDefinition); } - DatatypeDef datatypeDefinition = pullAnnotation(theClass, theClass, DatatypeDef.class); + DatatypeDef datatypeDefinition = pullAnnotation(theClass, DatatypeDef.class); if (datatypeDefinition != null) { if (ICompositeType.class.isAssignableFrom(theClass)) { @SuppressWarnings("unchecked") @@ -325,17 +273,19 @@ class ModelScanner { scanPrimitiveDatatype(resClass, datatypeDefinition); } else { return; -// throw new ConfigurationException("Resource type contains a @" + DatatypeDef.class.getSimpleName() + " annotation but does not implement " + IDatatype.class.getCanonicalName() + ": " + theClass.getCanonicalName()); + // throw new ConfigurationException("Resource type contains a @" + DatatypeDef.class.getSimpleName() + " annotation but does not implement " + IDatatype.class.getCanonicalName() + ": " + + // theClass.getCanonicalName()); } } - Block blockDefinition = pullAnnotation(theClass, theClass, Block.class); + Block blockDefinition = pullAnnotation(theClass, Block.class); if (blockDefinition != null) { if (IResourceBlock.class.isAssignableFrom(theClass) || IBaseBackboneElement.class.isAssignableFrom(theClass) || IBaseDatatypeElement.class.isAssignableFrom(theClass)) { scanBlock(theClass); } else { - throw new ConfigurationException("Type contains a @" + Block.class.getSimpleName() + " annotation but does not implement " + IResourceBlock.class.getCanonicalName() + ": " + theClass.getCanonicalName()); + throw new ConfigurationException( + "Type contains a @" + Block.class.getSimpleName() + " annotation but does not implement " + IResourceBlock.class.getCanonicalName() + ": " + theClass.getCanonicalName()); } } @@ -364,8 +314,8 @@ class ModelScanner { RuntimeCompositeDatatypeDefinition resourceDef; if (theClass.equals(ExtensionDt.class)) { resourceDef = new RuntimeExtensionDtDefinition(theDatatypeDefinition, theClass, true); -// } else if (IBaseMetaType.class.isAssignableFrom(theClass)) { -// resourceDef = new RuntimeMetaDefinition(theDatatypeDefinition, theClass, isStandardType(theClass)); + // } else if (IBaseMetaType.class.isAssignableFrom(theClass)) { + // resourceDef = new RuntimeMetaDefinition(theDatatypeDefinition, theClass, isStandardType(theClass)); } else { resourceDef = new RuntimeCompositeDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass)); } @@ -429,7 +379,8 @@ class ModelScanner { } @SuppressWarnings("unchecked") - private void scanCompositeElementForChildren(Class theClass, Set elementNames, TreeMap theOrderToElementDef, TreeMap theOrderToExtensionDef) { + 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()) { @@ -439,16 +390,16 @@ class ModelScanner { continue; } - Child childAnnotation = pullAnnotation(theClass, next, Child.class); + 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(theClass, next, Description.class); + Description descriptionAnnotation = pullAnnotation(next, Description.class); TreeMap orderMap = theOrderToElementDef; - Extension extensionAttr = pullAnnotation(theClass, next, Extension.class); + Extension extensionAttr = pullAnnotation(next, Extension.class); if (extensionAttr != null) { orderMap = theOrderToExtensionDef; } @@ -471,8 +422,8 @@ class ModelScanner { } } 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()); + 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 { @@ -487,8 +438,8 @@ class ModelScanner { } } 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()); + 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()); } } @@ -504,8 +455,7 @@ class ModelScanner { // 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 + * 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.MIN_VALUE; @@ -572,7 +522,8 @@ class ModelScanner { binder = getBoundCodeBinder(next); } - RuntimeChildDeclaredExtensionDefinition def = new RuntimeChildDeclaredExtensionDefinition(next, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et, binder); + RuntimeChildDeclaredExtensionDefinition def = new RuntimeChildDeclaredExtensionDefinition(next, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et, + binder); if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) { def.setEnumerationType(ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next)); @@ -589,7 +540,8 @@ class ModelScanner { List> refTypesList = new ArrayList>(); for (Class nextType : childAnnotation.type()) { 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()); + 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); @@ -597,10 +549,10 @@ class ModelScanner { 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)) { + } 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? + * 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; @@ -608,13 +560,14 @@ class ModelScanner { 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)) { + } 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)) { + } 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); @@ -642,10 +595,11 @@ class ModelScanner { } } - CodeableConceptElement concept = pullAnnotation(theClass, next, CodeableConceptElement.class); + CodeableConceptElement concept = pullAnnotation(next, CodeableConceptElement.class); if (concept != null) { if (!ICodedDatatype.class.isAssignableFrom(nextDatatype)) { - throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is marked as @" + CodeableConceptElement.class.getCanonicalName() + " but type is not a subtype of " + ICodedDatatype.class.getName()); + throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is marked as @" + CodeableConceptElement.class.getCanonicalName() + + " but type is not a subtype of " + ICodedDatatype.class.getName()); } else { Class type = concept.type(); myScanAlsoCodeTable.add(type); @@ -708,21 +662,23 @@ class ModelScanner { Class parent = theClass.getSuperclass(); primaryNameProvider = false; while (parent.equals(Object.class) == false && isBlank(resourceName)) { - ResourceDef nextDef = pullAnnotation(theClass, parent, ResourceDef.class); + ResourceDef nextDef = pullAnnotation(parent, ResourceDef.class); if (nextDef != null) { resourceName = nextDef.name(); } parent = parent.getSuperclass(); } if (isBlank(resourceName)) { - throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name(): " + theClass.getCanonicalName() + " - This is only allowed for types that extend other resource types "); + throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name(): " + theClass.getCanonicalName() + + " - This is only allowed for types that extend other resource types "); } } String resourceId = resourceDefinition.id(); if (!isBlank(resourceId)) { if (myIdToResourceDefinition.containsKey(resourceId)) { - throw new ConfigurationException("The following resource types have the same ID of '" + resourceId + "' - " + theClass.getCanonicalName() + " and " + myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName()); + throw new ConfigurationException("The following resource types have the same ID of '" + resourceId + "' - " + theClass.getCanonicalName() + " and " + + myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName()); } } @@ -748,7 +704,7 @@ class ModelScanner { Map compositeFields = new LinkedHashMap(); for (Field nextField : theClass.getFields()) { - SearchParamDefinition searchParam = pullAnnotation(theClass, nextField, SearchParamDefinition.class); + SearchParamDefinition searchParam = pullAnnotation(nextField, SearchParamDefinition.class); if (searchParam != null) { RestSearchParameterTypeEnum paramType = RestSearchParameterTypeEnum.valueOf(searchParam.type().toUpperCase()); if (paramType == null) { @@ -758,7 +714,17 @@ class ModelScanner { compositeFields.put(nextField, searchParam); continue; } - RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType); + + Set providesMembershipInCompartments = null; + Compartments compartmentsAnnotation = pullAnnotation(nextField, Compartments.class); + if (compartmentsAnnotation != null) { + providesMembershipInCompartments = new HashSet(); + for (Compartment next : compartmentsAnnotation.providesMembershipIn()) { + providesMembershipInCompartments.add(next.name()); + } + } + + RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType, providesMembershipInCompartments); theResourceDef.addSearchParam(param); nameToParam.put(param.getName(), param); } @@ -771,13 +737,14 @@ class ModelScanner { for (String nextName : searchParam.compositeOf()) { RuntimeSearchParam param = nameToParam.get(nextName); if (param == null) { - ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}", new Object[] { theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet() }); + ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}", + new Object[] { theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet() }); continue; } compositeOf.add(param); } - RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf); + RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf, null); theResourceDef.addSearchParam(param); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java index e7eb498daa8..c8ccc62ae68 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.context; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.StringTokenizer; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; @@ -29,23 +30,30 @@ import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; public class RuntimeSearchParam { + private List myCompositeOf; private String myDescription; private String myName; private RestSearchParameterTypeEnum myParamType; private String myPath; - private List myCompositeOf; + private Set myProvidesMembershipInCompartments; - public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType) { - this(theName, theDescription, thePath, theParamType, null); - } - - public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf) { + public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf, + Set theProvidesMembershipInCompartments) { super(); myName = theName; myDescription = theDescription; myPath = thePath; myParamType = theParamType; myCompositeOf = theCompositeOf; + if (theProvidesMembershipInCompartments != null && !theProvidesMembershipInCompartments.isEmpty()) { + myProvidesMembershipInCompartments = Collections.unmodifiableSet(theProvidesMembershipInCompartments); + } else { + myProvidesMembershipInCompartments = null; + } + } + + public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set theProvidesMembershipInCompartments) { + this(theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments); } public List getCompositeOf() { @@ -70,10 +78,10 @@ public class RuntimeSearchParam { public List getPathsSplit() { String path = getPath(); - if (path.indexOf('|')==-1) { + if (path.indexOf('|') == -1) { return Collections.singletonList(path); } - + List retVal = new ArrayList(); StringTokenizer tok = new StringTokenizer(path, "|"); while (tok.hasMoreElements()) { @@ -83,4 +91,11 @@ public class RuntimeSearchParam { return retVal; } + /** + * Can return null + */ + public Set getProvidesMembershipInCompartments() { + return myProvidesMembershipInCompartments; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Compartment.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Compartment.java new file mode 100644 index 00000000000..3a8df120949 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Compartment.java @@ -0,0 +1,19 @@ +package ca.uhn.fhir.model.api.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(value= {}) +public @interface Compartment { + + /** + * This may only be populated on a reference field. On such a field, places the containing + * resource in a compartment with the name(s) specified by the given strings, where the compartment + * belongs to the target resource. For example, this field could be populated with Patient on + * the Observation.subject field. + */ + String name(); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Compartments.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Compartments.java new file mode 100644 index 00000000000..826b6f95321 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Compartments.java @@ -0,0 +1,14 @@ +package ca.uhn.fhir.model.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(value= {ElementType.FIELD}) +public @interface Compartments { + + Compartment[] providesMembershipIn(); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index bc55967eb8d..126fc47f0fb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -38,54 +38,35 @@ public class DaoConfig { // update setter javadoc if default changes // *** private boolean myAllowInlineMatchUrlReferences = false; - - /** - * @see #setAllowInlineMatchUrlReferences(boolean) - */ - public boolean isAllowInlineMatchUrlReferences() { - return myAllowInlineMatchUrlReferences; - } - /** - * Should references containing match URLs be resolved and replaced in create and update operations. For - * example, if this property is set to true and a resource is created containing a reference - * to "Patient?identifier=12345", this is reference match URL will be resolved and replaced according - * to the usual match URL rules. - *

- * Default is false for now, as this is an experimental feature. - *

- * @since 1.5 - */ - public void setAllowInlineMatchUrlReferences(boolean theAllowInlineMatchUrlReferences) { - myAllowInlineMatchUrlReferences = theAllowInlineMatchUrlReferences; - } - - private boolean myAllowMultipleDelete; + private boolean myAllowMultipleDelete; private int myHardSearchLimit = 1000; private int myHardTagListLimit = 1000; private int myIncludeLimit = 2000; + + // *** + // update setter javadoc if default changes + // *** + private boolean myIndexContainedResources = true; + private List myInterceptors; private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC; private boolean mySchedulingDisabled; private boolean mySubscriptionEnabled; private long mySubscriptionPollDelay = 1000; private Long mySubscriptionPurgeInactiveAfterMillis; - /** * See {@link #setIncludeLimit(int)} */ public int getHardSearchLimit() { return myHardSearchLimit; } - public int getHardTagListLimit() { return myHardTagListLimit; } - public int getIncludeLimit() { return myIncludeLimit; } - /** * Returns the interceptors which will be notified of operations. * @@ -110,10 +91,25 @@ public class DaoConfig { return mySubscriptionPurgeInactiveAfterMillis; } + /** + * @see #setAllowInlineMatchUrlReferences(boolean) + */ + public boolean isAllowInlineMatchUrlReferences() { + return myAllowInlineMatchUrlReferences; + } + public boolean isAllowMultipleDelete() { return myAllowMultipleDelete; } + /** + * Should contained IDs be indexed the same way that non-contained IDs are (default is + * true) + */ + public boolean isIndexContainedResources() { + return myIndexContainedResources; + } + public boolean isSchedulingDisabled() { return mySchedulingDisabled; } @@ -125,6 +121,20 @@ public class DaoConfig { return mySubscriptionEnabled; } + /** + * Should references containing match URLs be resolved and replaced in create and update operations. For + * example, if this property is set to true and a resource is created containing a reference + * to "Patient?identifier=12345", this is reference match URL will be resolved and replaced according + * to the usual match URL rules. + *

+ * Default is false for now, as this is an experimental feature. + *

+ * @since 1.5 + */ + public void setAllowInlineMatchUrlReferences(boolean theAllowInlineMatchUrlReferences) { + myAllowInlineMatchUrlReferences = theAllowInlineMatchUrlReferences; + } + public void setAllowMultipleDelete(boolean theAllowMultipleDelete) { myAllowMultipleDelete = theAllowMultipleDelete; } @@ -146,6 +156,14 @@ public class DaoConfig { myIncludeLimit = theIncludeLimit; } + /** + * Should contained IDs be indexed the same way that non-contained IDs are (default is + * true) + */ + public void setIndexContainedResources(boolean theIndexContainedResources) { + myIndexContainedResources = theIndexContainedResources; + } + /** * This may be used to optionally register server interceptors directly against the DAOs. *

diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java index 52666ec75c6..1fd44201d30 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.entity; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.Entity; +import javax.persistence.Index; import javax.persistence.Table; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -34,9 +35,8 @@ import org.hibernate.search.annotations.Field; //@formatter:off @Embeddable @Entity -@Table(name = "HFJ_SPIDX_COORDS" /* , indexes = { @Index(name = "IDX_SP_TOKEN", columnList = "SP_SYSTEM,SP_VALUE") } */) -@org.hibernate.annotations.Table(appliesTo = "HFJ_SPIDX_COORDS", indexes = { - @org.hibernate.annotations.Index(name = "IDX_SP_COORDS", columnNames = { "RES_TYPE", "SP_NAME", "SP_LATITUDE" }) +@Table(name = "HFJ_SPIDX_COORDS", indexes = { + @Index(name = "IDX_SP_COORDS", columnList = "RES_TYPE,SP_NAME,SP_LATITUDE,SP_LONGITUDE") }) //@formatter:on public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchParam { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index 8c04aa591ba..73604ef8c37 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -144,6 +144,9 @@ public class ResourceTable extends BaseHasResource implements Serializable { //@formatter:on private String myContentText; + @Column(name = "HAS_CONTAINED", nullable = true) + private boolean myHasContainedResource; + @Column(name = "SP_HAS_LINKS") private boolean myHasLinks; @@ -158,6 +161,9 @@ public class ResourceTable extends BaseHasResource implements Serializable { @Column(name = "SP_INDEX_STATUS", nullable = true) private Long myIndexStatus; + @Column(name = "IS_CONTAINED", nullable = true) + private boolean myIsContainedResource; + @Column(name = "RES_LANGUAGE", length = MAX_LANGUAGE_LENGTH, nullable = true) private String myLanguage; @@ -173,13 +179,13 @@ public class ResourceTable extends BaseHasResource implements Serializable { @Column(name = "SP_COORDS_PRESENT") private boolean myParamsCoordsPopulated; - + @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection myParamsDate; @Column(name = "SP_DATE_PRESENT") private boolean myParamsDatePopulated; - + @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection myParamsNumber; @@ -340,10 +346,18 @@ public class ResourceTable extends BaseHasResource implements Serializable { return false; } + public boolean isHasContainedResource() { + return myHasContainedResource; + } + public boolean isHasLinks() { return myHasLinks; } + public boolean isIsContainedResource() { + return myIsContainedResource; + } + public boolean isParamsCoordsPopulated() { return myParamsCoordsPopulated; } @@ -376,6 +390,10 @@ public class ResourceTable extends BaseHasResource implements Serializable { myContentText = theContentText; } + public void setHasContainedResource(boolean theHasContainedResource) { + myHasContainedResource = theHasContainedResource; + } + public void setHasLinks(boolean theHasLinks) { myHasLinks = theHasLinks; } @@ -388,6 +406,10 @@ public class ResourceTable extends BaseHasResource implements Serializable { myIndexStatus = theIndexStatus; } + public void setIsContainedResource(boolean theIsContainedResource) { + myIsContainedResource = theIsContainedResource; + } + public void setLanguage(String theLanguage) { if (defaultString(theLanguage).length() > MAX_LANGUAGE_LENGTH) { throw new UnprocessableEntityException("Language exceeds maximum length of " + MAX_LANGUAGE_LENGTH + " chars: " + theLanguage); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index 398d137f2c8..6b448f2a4a8 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -46,6 +46,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallback; @@ -225,14 +226,14 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { public TransactionTemplate newTxTemplate() { TransactionTemplate retVal = new TransactionTemplate(myTxManager); - retVal.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + retVal.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); retVal.afterPropertiesSet(); return retVal; } public static void purgeDatabase(final EntityManager entityManager, PlatformTransactionManager theTxManager) { TransactionTemplate txTemplate = new TransactionTemplate(theTxManager); - txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.execute(new TransactionCallback() { @Override public Void doInTransaction(TransactionStatus theStatus) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ContainedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ContainedTest.java new file mode 100644 index 00000000000..9fceb615e08 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ContainedTest.java @@ -0,0 +1,58 @@ +package ca.uhn.fhir.jpa.dao.dstu3; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertThat; + +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.Test; + +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.TokenParamModifier; + +public class FhirResourceDaoDstu3ContainedTest extends BaseJpaDstu3Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3ContainedTest.class); + + @Test + public void before() { + myDaoConfig.setIndexContainedResources(true); + } + + @Test + public void testIndexContained() { + Patient p = new Patient(); + p.setId("#some_patient"); + p.addName().addFamily("MYFAMILY").addGiven("MYGIVEN"); + + Observation o1 = new Observation(); + o1.getCode().setText("Some Observation"); + o1.setSubject(new Reference(p)); + IIdType oid1 = myObservationDao.create(o1).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.getCode().setText("Some Observation"); + o2.setSubject(new Reference(p)); + IIdType oid2 = myObservationDao.create(o2).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addName().addFamily("MYFAMILY").addGiven("MYGIVEN"); + IIdType pid2 = myPatientDao.create(p2).getId().toUnqualifiedVersionless(); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(o2)); + + + SearchParameterMap map; + +// map = new SearchParameterMap(); +// map.add(Observation.SP_CODE, new TokenParam(null, "some observation").setModifier(TokenParamModifier.TEXT)); +// assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2))); + + } + + + // TODO: make sure match URLs don't delete + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchFtTest.java index 8df9c19757d..d29d178c334 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchFtTest.java @@ -31,6 +31,7 @@ import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.Constants; public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { @@ -56,15 +57,15 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { SearchParameterMap map; map = new SearchParameterMap(); - map.add(Observation.SP_CODE, new TokenParam(null, "blood").setText(true)); + map.add(Observation.SP_CODE, new TokenParam(null, "blood").setModifier(TokenParamModifier.TEXT)); assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2))); map = new SearchParameterMap(); - map.add(Observation.SP_CODE, new TokenParam(null, "blood").setText(true)); + map.add(Observation.SP_CODE, new TokenParam(null, "blood").setModifier(TokenParamModifier.TEXT)); assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty()); map = new SearchParameterMap(); - map.add(Observation.SP_CODE, new TokenParam(null, "blood").setText(true)); + map.add(Observation.SP_CODE, new TokenParam(null, "blood").setModifier(TokenParamModifier.TEXT)); map.add(Constants.PARAM_CONTENT, new StringParam("obs1")); assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1))); @@ -358,7 +359,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { Device dev2 = new Device(); dev2.setManufacturer("Some Manufacturer 2"); - IIdType devId2 = myDeviceDao.create(dev2).getId().toUnqualifiedVersionless(); + myDeviceDao.create(dev2).getId().toUnqualifiedVersionless(); Observation obs1 = new Observation(); obs1.getSubject().setReferenceElement(ptId1); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java index 2669598952f..c182581def3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java @@ -23,9 +23,9 @@ import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.dstu3.model.BaseResource; import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.dstu3.model.DateTimeType; import org.hl7.fhir.dstu3.model.DateType; import org.hl7.fhir.dstu3.model.Device; @@ -46,12 +46,12 @@ import org.hl7.fhir.dstu3.model.Quantity; import org.hl7.fhir.dstu3.model.Reference; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.Subscription; +import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; import org.hl7.fhir.dstu3.model.Substance; import org.hl7.fhir.dstu3.model.TemporalPrecisionEnum; import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; -import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; -import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.Ignore; @@ -68,7 +68,6 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.entity.ResourceLink; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.param.CompositeParam; @@ -585,21 +584,21 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { } { Map params = new HashMap(); - params.put(BaseResource.SP_RES_LANGUAGE, new StringParam("en_CA")); + params.put(IAnyResource.SP_RES_LANGUAGE, new StringParam("en_CA")); List patients = toList(myPatientDao.search(params)); assertEquals(1, patients.size()); assertEquals(id1.toUnqualifiedVersionless(), patients.get(0).getIdElement().toUnqualifiedVersionless()); } { Map params = new HashMap(); - params.put(BaseResource.SP_RES_LANGUAGE, new StringParam("en_US")); + params.put(IAnyResource.SP_RES_LANGUAGE, new StringParam("en_US")); List patients = toList(myPatientDao.search(params)); assertEquals(1, patients.size()); assertEquals(id2.toUnqualifiedVersionless(), patients.get(0).getIdElement().toUnqualifiedVersionless()); } { Map params = new HashMap(); - params.put(BaseResource.SP_RES_LANGUAGE, new StringParam("en_GB")); + params.put(IAnyResource.SP_RES_LANGUAGE, new StringParam("en_GB")); List patients = toList(myPatientDao.search(params)); assertEquals(0, patients.size()); } @@ -628,18 +627,18 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { } { SearchParameterMap params = new SearchParameterMap(); - params.add(BaseResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US"))); + params.add(IAnyResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US"))); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1, id2)); } { SearchParameterMap params = new SearchParameterMap(); - params.add(BaseResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US"))); + params.add(IAnyResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US"))); params.setLastUpdated(new DateRangeParam(betweenTime, null)); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id2)); } { SearchParameterMap params = new SearchParameterMap(); - params.add(BaseResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + params.add(IAnyResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); } { @@ -647,7 +646,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { StringAndListParam and = new StringAndListParam(); and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA"))); - params.add(BaseResource.SP_RES_LANGUAGE, and); + params.add(IAnyResource.SP_RES_LANGUAGE, and); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); } { @@ -655,7 +654,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { StringAndListParam and = new StringAndListParam(); and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); and.addAnd(new StringOrListParam().addOr(new StringParam("ZZZZZ"))); - params.add(BaseResource.SP_RES_LANGUAGE, and); + params.add(IAnyResource.SP_RES_LANGUAGE, and); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty()); } { @@ -663,7 +662,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { StringAndListParam and = new StringAndListParam(); and.addAnd(new StringOrListParam().addOr(new StringParam("ZZZZZ"))); and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); - params.add(BaseResource.SP_RES_LANGUAGE, and); + params.add(IAnyResource.SP_RES_LANGUAGE, and); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty()); } { @@ -671,7 +670,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { StringAndListParam and = new StringAndListParam(); and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null))); - params.add(BaseResource.SP_RES_LANGUAGE, and); + params.add(IAnyResource.SP_RES_LANGUAGE, and); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); } { @@ -680,7 +679,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { StringAndListParam and = new StringAndListParam(); and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null))); - params.add(BaseResource.SP_RES_LANGUAGE, and); + params.add(IAnyResource.SP_RES_LANGUAGE, and); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); } { @@ -688,7 +687,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { StringAndListParam and = new StringAndListParam(); and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null))); - params.add(BaseResource.SP_RES_LANGUAGE, and); + params.add(IAnyResource.SP_RES_LANGUAGE, and); params.add("_id", new StringParam(id1.getIdPart())); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); } @@ -766,8 +765,6 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { @Test public void testSearchLastUpdatedParamWithComparator() throws InterruptedException { - String methodName = "testSearchLastUpdatedParamWithComparator"; - IIdType id0; { Patient patient = new Patient(); @@ -780,7 +777,6 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { long start = System.currentTimeMillis(); Thread.sleep(sleep); - DateTimeType beforeAny = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI); IIdType id1a; { Patient patient = new Patient(); @@ -814,17 +810,17 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); map = new SearchParameterMap(); - map.setLastUpdated(new DateRangeParam(new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, startDateTime), new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, endDateTime))); + map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, startDateTime), new DateParam(ParamPrefixEnum.LESSTHAN_OR_EQUALS, endDateTime))); ourLog.info("Searching: {}", map.getLastUpdated()); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); map = new SearchParameterMap(); - map.setLastUpdated(new DateRangeParam(new DateParam(QuantityCompararatorEnum.GREATERTHAN, startDateTime), new DateParam(QuantityCompararatorEnum.LESSTHAN, endDateTime))); + map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN, startDateTime), new DateParam(ParamPrefixEnum.LESSTHAN, endDateTime))); ourLog.info("Searching: {}", map.getLastUpdated()); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); map = new SearchParameterMap(); - map.setLastUpdated(new DateRangeParam(new DateParam(QuantityCompararatorEnum.GREATERTHAN, startDateTime.getValue()), new DateParam(QuantityCompararatorEnum.LESSTHAN, myPatientDao.read(id1b).getMeta().getLastUpdatedElement().getValue()))); + map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN, startDateTime.getValue()), new DateParam(ParamPrefixEnum.LESSTHAN, myPatientDao.read(id1b).getMeta().getLastUpdatedElement().getValue()))); ourLog.info("Searching: {}", map.getLastUpdated()); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a)); } @@ -1372,7 +1368,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { QuantityParam param; Set found; - param = new QuantityParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, null); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, null); found = myObservationDao.searchForIds("value-quantity", param); int initialSize = found.size(); @@ -1383,19 +1379,19 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { myObservationDao.create(o); - param = new QuantityParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, null); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, null); found = myObservationDao.searchForIds("value-quantity", param); assertEquals(1 + initialSize, found.size()); - param = new QuantityParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, methodName + "units"); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, methodName + "units"); found = myObservationDao.searchForIds("value-quantity", param); assertEquals(1, found.size()); - param = new QuantityParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), "urn:bar:" + methodName, null); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), "urn:bar:" + methodName, null); found = myObservationDao.searchForIds("value-quantity", param); assertEquals(1, found.size()); - param = new QuantityParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), "urn:bar:" + methodName, methodName + "units"); + param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), "urn:bar:" + methodName, methodName + "units"); found = myObservationDao.searchForIds("value-quantity", param); assertEquals(1, found.size()); @@ -1405,8 +1401,8 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { public void testSearchWithEmptySort() { SearchParameterMap criteriaUrl = new SearchParameterMap(); DateRangeParam range = new DateRangeParam(); - range.setLowerBound(new DateParam(QuantityCompararatorEnum.GREATERTHAN, 1000000)); - range.setUpperBound(new DateParam(QuantityCompararatorEnum.LESSTHAN, 2000000)); + range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, 1000000)); + range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, 2000000)); criteriaUrl.setLastUpdated(range); criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC)); IBundleProvider results = myObservationDao.search(criteriaUrl); @@ -1554,7 +1550,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { { SearchParameterMap params = new SearchParameterMap(); - params.add(BaseResource.SP_RES_ID, new StringParam(orgId.getIdPart())); + params.add(IAnyResource.SP_RES_ID, new StringParam(orgId.getIdPart())); params.addInclude(Organization.INCLUDE_PARTOF.asNonRecursive()); List resources = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); assertThat(resources, contains(orgId, parentOrgId)); @@ -1596,7 +1592,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { { SearchParameterMap params = new SearchParameterMap(); - params.add(Organization.SP_RES_ID, new StringParam(orgId.getIdPart())); + params.add(IAnyResource.SP_RES_ID, new StringParam(orgId.getIdPart())); params.addInclude(Organization.INCLUDE_PARTOF.asRecursive()); List resources = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); ourLog.info(resources.toString()); @@ -1891,7 +1887,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { ourLog.info("Initial size: " + value.size()); for (IBaseResource next : value.getResources(0, value.size())) { ourLog.info("Deleting: {}", next.getIdElement()); - myDeviceDao.delete((IIdType) next.getIdElement()); + myDeviceDao.delete(next.getIdElement()); } value = myDeviceDao.search(new SearchParameterMap()); @@ -2098,7 +2094,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { ValueSet vs2 = new ValueSet(); vs2.setUrl("http://hl7.org/foo/bar"); - IIdType id2 = myValueSetDao.create(vs2).getId().toUnqualifiedVersionless(); + myValueSetDao.create(vs2).getId().toUnqualifiedVersionless(); IBundleProvider result; result = myValueSetDao.search(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/basic-resource-type")); diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/CompartmentGeneratorMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/CompartmentGeneratorMojo.java deleted file mode 100644 index 843189ae17a..00000000000 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/CompartmentGeneratorMojo.java +++ /dev/null @@ -1,35 +0,0 @@ -package ca.uhn.fhir.tinder; - -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Parameter; - -import ca.uhn.fhir.tinder.parser.CompartmentParser; - -public class CompartmentGeneratorMojo extends AbstractMojo { - - @Parameter(required = true) - private String fhirVersion; - - @Override - public void execute() throws MojoExecutionException, MojoFailureException { - CompartmentParser p = new CompartmentParser(fhirVersion); - try { - p.parse(); - } catch (MojoExecutionException e) { - throw e; - } catch (MojoFailureException e) { - throw e; - } catch (Exception e) { - throw new MojoFailureException("Failure during parse", e); - } - } - - public static void main(String[] args) throws MojoExecutionException, MojoFailureException { - CompartmentGeneratorMojo mojo = new CompartmentGeneratorMojo(); - mojo.fhirVersion = "dstu2"; - mojo.execute(); - } - -} diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderStructuresMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderStructuresMojo.java index f4d9a368819..890d35745ce 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderStructuresMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderStructuresMojo.java @@ -228,7 +228,7 @@ public class TinderStructuresMojo extends AbstractMojo { String dtOutputDir = "target/generated-sources/tinder/ca/uhn/fhir/model/dev/composite"; ResourceGeneratorUsingSpreadsheet rp = new ResourceGeneratorUsingSpreadsheet("dstu2", "."); - rp.setBaseResourceNames(Arrays.asList( "supplyrequest" + rp.setBaseResourceNames(Arrays.asList( "patient", "auditevent" , "observation" // //, "contract" // "valueset", "organization", "location" // , "observation", "conformance" diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/Resource.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/Resource.java index 1b53906c08c..3b9e60a3f80 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/Resource.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/Resource.java @@ -1,5 +1,8 @@ package ca.uhn.fhir.tinder.model; +import java.util.ArrayList; +import java.util.List; + public class Resource extends BaseRootType { @Override @@ -20,4 +23,20 @@ public class Resource extends BaseRootType { return name; } + public SearchParameter getSearchParameterByName(String theName) { + for (SearchParameter next : getSearchParameters()) { + if (next.getName().equalsIgnoreCase(theName)) { + return next; + } + } + return null; + } + public List getSearchParameterNames() { + ArrayList retVal = new ArrayList(); + for (SearchParameter next : getSearchParameters()) { + retVal.add(next.getName()); + } + return retVal; + } + } diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java index b91e2aa668a..201d88dc92b 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java @@ -10,6 +10,7 @@ import org.apache.commons.lang3.StringUtils; public class SearchParameter { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameter.class); + private List myCompartments = new ArrayList(); private List myCompositeOf; private List myCompositeTypes; private String myDescription; @@ -18,7 +19,6 @@ public class SearchParameter { private String myResourceName; private List myTargetTypes; private String myType; - private String myVersion; public SearchParameter(String theVersion, String theResourceName) { @@ -26,6 +26,14 @@ public class SearchParameter { this.myResourceName = theResourceName; } + public void addCompartment(String theCompartment) { + myCompartments.add(theCompartment); + } + + public List getCompartments() { + return myCompartments; + } + public List getCompositeOf() { if (myCompositeOf == null) { myCompositeOf = new ArrayList(); diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureSpreadsheetParser.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureSpreadsheetParser.java index c13a6afa0ce..14ef3483899 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureSpreadsheetParser.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureSpreadsheetParser.java @@ -16,6 +16,7 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -404,7 +405,7 @@ public abstract class BaseStructureSpreadsheetParser extends BaseStructureParser /** * Subclasses may override */ - protected void postProcess(BaseElement theTarget) { + protected void postProcess(BaseElement theTarget) throws MojoFailureException { // nothing } diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/CompartmentParser.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/CompartmentParser.java index 56ee4fe4485..29ce20ae055 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/CompartmentParser.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/CompartmentParser.java @@ -1,20 +1,31 @@ package ca.uhn.fhir.tinder.parser; +import static org.apache.commons.lang3.StringUtils.isBlank; + import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; import org.apache.maven.plugin.MojoFailureException; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import ca.uhn.fhir.tinder.model.BaseElement; +import ca.uhn.fhir.tinder.model.Resource; +import ca.uhn.fhir.tinder.model.SearchParameter; import ca.uhn.fhir.tinder.util.XMLUtils; public class CompartmentParser { private String myVersion; + private Resource myResourceDef; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompartmentParser.class); - public CompartmentParser(String theVersion) { + public CompartmentParser(String theVersion, Resource theResourceDef) { myVersion = theVersion; + myResourceDef = theResourceDef; } public void parse() throws Exception { @@ -45,6 +56,67 @@ public class CompartmentParser { throw new Exception("Failed to find worksheet with name 'Data Elements' in spreadsheet: " + resName); } + Element table = (Element) resourcesSheet.getElementsByTagName("Table").item(0); + NodeList rows = table.getElementsByTagName("Row"); + + Map col2compartment = new HashMap(); + Element headerRow = (Element) rows.item(0); + for (int i = 1; i < headerRow.getElementsByTagName("Cell").getLength(); i++) { + Element cellElement = (Element) headerRow.getElementsByTagName("Cell").item(i); + Element dataElement = (Element) cellElement.getElementsByTagName("Data").item(0); + col2compartment.put(i, dataElement.getTextContent()); + } + + Element row = null; + for (int i = 1; i < rows.getLength(); i++) { + Element nextRow = (Element) rows.item(i); + + NodeList cells = nextRow.getElementsByTagName("Cell"); + Element cellElement = (Element) cells.item(0); + Element dataElement = (Element) cellElement.getElementsByTagName("Data").item(0); + if (dataElement.getTextContent().equals(myResourceDef.getName())) { + row = nextRow; + break; + } + } + + if (row == null) { + ourLog.debug("No compartments for resource {}", myResourceDef.getName()); + return; + } + + NodeList cells = row.getElementsByTagName("Cell"); + for (int i = 1; i < cells.getLength(); i++) { + Element cellElement = (Element) cells.item(i); + int index = i; + if (cellElement.hasAttribute("Index")) { + index = Integer.parseInt(cellElement.getAttribute("Index")); + } + + String compartment = col2compartment.get(index); + + Element dataElement = (Element) cellElement.getElementsByTagName("Data").item(0); + String namesUnsplit = dataElement.getTextContent(); + String[] namesSplit = namesUnsplit.split("\\|"); + for (String nextName : namesSplit) { + nextName = nextName.trim(); + if (isBlank(nextName)) { + continue; + } + + String[] parts = nextName.split("\\."); + if (parts[0].equals("{def}")) { + continue; + } + Resource element = myResourceDef; + SearchParameter sp = element.getSearchParameterByName(parts[0]); + if (sp == null) { + throw new MojoFailureException("Can't find child named " + parts[0] + " - Valid names: " + element.getSearchParameterNames()); + } + + sp.addCompartment(compartment); + } + } } } diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/DatatypeGeneratorUsingSpreadsheet.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/DatatypeGeneratorUsingSpreadsheet.java index b219761afa9..8045985edd5 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/DatatypeGeneratorUsingSpreadsheet.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/DatatypeGeneratorUsingSpreadsheet.java @@ -22,7 +22,7 @@ import com.google.common.reflect.ClassPath.ClassInfo; public class DatatypeGeneratorUsingSpreadsheet extends BaseStructureSpreadsheetParser { @Override - protected void postProcess(BaseElement theTarget) { + protected void postProcess(BaseElement theTarget) throws MojoFailureException { super.postProcess(theTarget); /* diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/ResourceGeneratorUsingSpreadsheet.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/ResourceGeneratorUsingSpreadsheet.java index 44cf091d25b..9ed90614419 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/ResourceGeneratorUsingSpreadsheet.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/ResourceGeneratorUsingSpreadsheet.java @@ -26,12 +26,21 @@ public class ResourceGeneratorUsingSpreadsheet extends BaseStructureSpreadsheetP } @Override - protected void postProcess(BaseElement theTarget) { + protected void postProcess(BaseElement theTarget) throws MojoFailureException { super.postProcess(theTarget); if ("Bundle".equals(theTarget.getName())) { addEverythingToSummary(theTarget); } + + if (getVersion().equals("dstu2") && theTarget instanceof Resource) { + try { + new CompartmentParser(getVersion(), (Resource) theTarget).parse(); + } catch (Exception e) { + throw new MojoFailureException(e.toString(), e); + } + } + } private void addEverythingToSummary(BaseElement theTarget) { diff --git a/hapi-tinder-plugin/src/main/resources/vm/resource.vm b/hapi-tinder-plugin/src/main/resources/vm/resource.vm index 9ac7c4e85d1..b83f5519c4d 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/resource.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/resource.vm @@ -56,6 +56,13 @@ public class ${className} extends ca.uhn.fhir.model.${version}.resource.BaseReso *

*/ @SearchParamDefinition(name="${param.name}", path="${param.path}", description="${param.description}", type="${param.type}" #{if}($param.compositeOf.empty == false) , compositeOf={ #{foreach}($compositeOf in $param.compositeOf) "${compositeOf}"#{if}($foreach.hasNext), #{end}#{end} } #{end} ) +#if ($param.compartments.size() > 0) + @Compartments(providesMembershipIn={ +#foreach ($compartment in $param.compartments) + @Compartment(name="$compartment") #{if}($foreach.hasNext), #{end} +#end + }) +#end public static final String $param.constantName = "${param.name}"; /**