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 f6c6a2e2bbc..981b67d2194 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)); @@ -600,10 +551,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; @@ -611,13 +562,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); @@ -645,10 +597,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); @@ -711,21 +664,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()); } } @@ -751,7 +706,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) { @@ -761,7 +716,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); } @@ -774,13 +739,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-base/src/main/java/ca/uhn/fhir/rest/annotation/ResourceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ResourceParam.java index 4ff18a50392..68b4f0d82b9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ResourceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ResourceParam.java @@ -25,6 +25,29 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import ca.uhn.fhir.rest.server.IResourceProvider; + +/** + * Denotes a parameter for a REST method which will contain the resource actually + * being created/updated/etc in an operation which contains a resource in the HTTP request. + *

+ * For example, in a {@link Create} operation the method parameter annotated with this + * annotation will contain the actual resource being created. + *

+ *

+ * Parameters with this annotation should typically be of the type of resource being + * operated on (see below for an exception when raw data is required). For example, in a + * {@link IResourceProvider} for Patient resources, the parameter annotated with this + * annotation should be of type Patient. + *

+ *

+ * Note that in servers it is also acceptable to have parameters with this annotation + * which are of type {@link String} or of type byte[]. Parameters of + * these types will contain the raw/unparsed HTTP request body. It is fine to + * have multiple parameters with this annotation, so you can have one parameter + * which accepts the parsed resource, and another which accepts the raw request. + *

+ */ @Target(value=ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ResourceParam { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java index 5159290b935..02469a665dc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java @@ -1,5 +1,8 @@ package ca.uhn.fhir.rest.method; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + /* * #%L * HAPI FHIR - Core Library @@ -46,6 +49,7 @@ import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; * @author Doug Martin (Regenstrief Center for Biomedical Informatics) */ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvocation { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHttpClientInvocationWithContents.class); private final Bundle myBundle; private final BundleTypeEnum myBundleType; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java index 0b65f425d7f..4977e93263a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java @@ -117,7 +117,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu @Override public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { - IResource resource = (IResource) theArgs[myResourceParameterIndex]; + IResource resource = (IResource) theArgs[myResourceParameterIndex]; // TODO: use IBaseResource if (resource == null) { throw new NullPointerException("Resource can not be null"); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java index 8b774627289..5dfc7fa9b0e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java @@ -451,10 +451,20 @@ public class MethodUtil { mode = Mode.RESOURCE; } else if (String.class.equals(parameterType)) { mode = ResourceParameter.Mode.BODY; + } else if (byte[].class.equals(parameterType)) { + mode = ResourceParameter.Mode.BODY_BYTE_ARRAY; } else if (EncodingEnum.class.equals(parameterType)) { mode = Mode.ENCODING; } else { - throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName()); + StringBuilder b = new StringBuilder(); + b.append("Method '"); + b.append(theMethod.getName()); + b.append("' is annotated with @"); + b.append(ResourceParam.class.getSimpleName()); + b.append(" but has a type that is not an implemtation of "); + b.append(IBaseResource.class.getCanonicalName()); + b.append(" or String or byte[]"); + throw new ConfigurationException(b.toString()); } param = new ResourceParameter((Class) parameterType, theProvider, mode); } else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java index 9650e3916a3..b9295c7da69 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java @@ -105,8 +105,10 @@ public class ResourceParameter implements IParameter { return IOUtils.toString(createRequestReader(theRequest)); } catch (IOException e) { // Shouldn't happen since we're reading from a byte array - throw new InternalErrorException("Failed to load request"); + throw new InternalErrorException("Failed to load request", e); } + case BODY_BYTE_ARRAY: + return theRequest.loadRequestContents(); case ENCODING: return RestfulServerUtils.determineRequestEncoding(theRequest); case RESOURCE: @@ -201,23 +203,27 @@ public class ResourceParameter implements IParameter { } public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { - IBaseResource retVal; + IBaseResource retVal = null; + if (IBaseBinary.class.isAssignableFrom(theResourceType)) { - FhirContext ctx = theRequest.getServer().getFhirContext(); String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); + if (EncodingEnum.forContentTypeStrict(ct) == null) { + FhirContext ctx = theRequest.getServer().getFhirContext(); IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance(); binary.setContentType(ct); binary.setContent(theRequest.loadRequestContents()); - retVal = binary; - } else { + } + } + + if (retVal == null) { retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType); } return retVal; } public enum Mode { - BODY, ENCODING, RESOURCE + BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java index 2a854e62764..cfa1bc0ca69 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java @@ -54,7 +54,7 @@ public class Constants { public static final String FORMAT_XML = "xml"; public static final String HEADER_ACCEPT = "Accept"; public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; - public static final String HEADER_ACCEPT_VALUE_ALL = CT_FHIR_XML + ";q=1.0, " + CT_FHIR_JSON + ";q=1.0"; + public static final String HEADER_ACCEPT_VALUE_XML_OR_JSON = CT_FHIR_XML + ";q=1.0, " + CT_FHIR_JSON + ";q=1.0"; public static final String HEADER_ALLOW = "Allow"; public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic "; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/EncodingEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/EncodingEnum.java index ceaae5bc886..29ddc0cf1c4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/EncodingEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/EncodingEnum.java @@ -44,6 +44,7 @@ public enum EncodingEnum { ; private static HashMap ourContentTypeToEncoding; + private static HashMap ourContentTypeToEncodingStrict; static { ourContentTypeToEncoding = new HashMap(); @@ -51,6 +52,9 @@ public enum EncodingEnum { ourContentTypeToEncoding.put(next.getBundleContentType(), next); ourContentTypeToEncoding.put(next.getResourceContentType(), next); } + + // Add before we add the lenient ones + ourContentTypeToEncodingStrict = new HashMap(ourContentTypeToEncoding); /* * These are wrong, but we add them just to be tolerant of other @@ -88,10 +92,30 @@ public enum EncodingEnum { return myResourceContentType; } + /** + * Returns the encoding for a given content type, or null if no encoding + * is found. + *

+ * This method is lenient! Things like "application/xml" will return {@link EncodingEnum#XML} + * even if the "+fhir" part is missing from the expected content type. + *

+ */ public static EncodingEnum forContentType(String theContentType) { return ourContentTypeToEncoding.get(theContentType); } + /** + * Returns the encoding for a given content type, or null if no encoding + * is found. + *

+ * This method is NOT lenient! Things like "application/xml" will return null + *

+ * @see #forContentType(String) + */ + public static EncodingEnum forContentTypeStrict(String theContentType) { + return ourContentTypeToEncodingStrict.get(theContentType); + } + public String getFormatContentType() { return myFormatContentType; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index 0f4b409cf23..be0f9944315 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -517,7 +517,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA)); } - public static boolean requestIsBrowser(HttpServletRequest theRequest) { - String userAgent = theRequest.getHeader("User-Agent"); - return userAgent != null && userAgent.contains("Mozilla"); - } - } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index fcde6e42738..6b7e5e1d9f5 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -43,6 +43,9 @@ ca.uhn.fhir.validation.ValidationResult.noIssuesDetected=No issues detected duri # JPA Messages ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request. +ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlInvalidResourceType=Invalid match URL "{0}" - Unknown resource type: "{1}" +ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlNoMatches=Invalid match URL "{0}" - No resources match this search +ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlMultipleMatches=Invalid match URL "{0}" - Multiple resources match this search ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index e4aaff0ccd1..9285b207d59 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -82,6 +82,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3; import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; @@ -147,19 +148,27 @@ import ca.uhn.fhir.util.OperationOutcomeUtil; public abstract class BaseHapiFhirDao implements IDao { + public static final long INDEX_STATUS_INDEXED = Long.valueOf(1L); + public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L); + public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile"; + public static final String OO_SEVERITY_ERROR = "error"; public static final String OO_SEVERITY_INFO = "information"; + public static final String OO_SEVERITY_WARN = "warning"; - /** - * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)} - */ - static final Map> RESOURCE_META_PARAMS; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class); + private static final Map ourRetrievalContexts = new HashMap(); /** * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)} */ static final Map>> RESOURCE_META_AND_PARAMS; + /** + * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)} + */ + static final Map> RESOURCE_META_PARAMS; + public static final String UCUM_NS = "http://unitsofmeasure.org"; static { Map> resourceMetaParams = new HashMap>(); Map>> resourceMetaAndParams = new HashMap>>(); @@ -177,14 +186,6 @@ public abstract class BaseHapiFhirDao implements IDao { RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams); } - public static final long INDEX_STATUS_INDEXED = Long.valueOf(1L); - public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L); - public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile"; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class); - - private static final Map ourRetrievalContexts = new HashMap(); - public static final String UCUM_NS = "http://unitsofmeasure.org"; - @Autowired(required = true) private DaoConfig myConfig; @@ -194,6 +195,9 @@ public abstract class BaseHapiFhirDao implements IDao { @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; + @Autowired + private IForcedIdDao myForcedIdDao; + @Autowired private PlatformTransactionManager myPlatformTransactionManager; @@ -222,23 +226,6 @@ public abstract class BaseHapiFhirDao implements IDao { return InstantDt.withCurrentTime(); } - public void setConfig(DaoConfig theConfig) { - myConfig = theConfig; - } - - public void setEntityManager(EntityManager theEntityManager) { - myEntityManager = theEntityManager; - } - - public void setPlatformTransactionManager(PlatformTransactionManager thePlatformTransactionManager) { - myPlatformTransactionManager = thePlatformTransactionManager; - } - - // @Override - // public void setResourceDaos(List> theResourceDaos) { - // myResourceDaos = theResourceDaos; - // } - protected Set extractResourceLinks(ResourceTable theEntity, IBaseResource theResource) { Set retVal = new HashSet(); @@ -377,6 +364,15 @@ public abstract class BaseHapiFhirDao implements IDao { return retVal; } + protected Set extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) { + return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource); + } + + // @Override + // public void setResourceDaos(List> theResourceDaos) { + // myResourceDaos = theResourceDaos; + // } + protected Set extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { return mySearchParamExtractor.extractSearchParamDates(theEntity, theResource); } @@ -385,14 +381,6 @@ public abstract class BaseHapiFhirDao implements IDao { return mySearchParamExtractor.extractSearchParamNumber(theEntity, theResource); } - protected Set extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) { - return mySearchParamExtractor.extractSearchParamUri(theEntity, theResource); - } - - protected Set extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) { - return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource); - } - protected Set extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) { return mySearchParamExtractor.extractSearchParamQuantity(theEntity, theResource); } @@ -405,6 +393,74 @@ public abstract class BaseHapiFhirDao implements IDao { return mySearchParamExtractor.extractSearchParamTokens(theEntity, theResource); } + protected Set extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) { + return mySearchParamExtractor.extractSearchParamUri(theEntity, theResource); + } + + private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set allDefs) { + TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource); + if (tagList != null) { + for (Tag next : tagList) { + TagDefinition tag = getTag(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel()); + allDefs.add(tag); + theEntity.addTag(tag); + theEntity.setHasTags(true); + } + } + + List securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource); + if (securityLabels != null) { + for (BaseCodingDt next : securityLabels) { + TagDefinition tag = getTag(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue()); + allDefs.add(tag); + theEntity.addTag(tag); + theEntity.setHasTags(true); + } + } + + List profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource); + if (profiles != null) { + for (IIdType next : profiles) { + TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null); + allDefs.add(tag); + theEntity.addTag(tag); + theEntity.setHasTags(true); + } + } + } + + private void extractTagsRi(IAnyResource theResource, ResourceTable theEntity, Set allDefs) { + List tagList = theResource.getMeta().getTag(); + if (tagList != null) { + for (IBaseCoding next : tagList) { + TagDefinition tag = getTag(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay()); + allDefs.add(tag); + theEntity.addTag(tag); + theEntity.setHasTags(true); + } + } + + List securityLabels = theResource.getMeta().getSecurity(); + if (securityLabels != null) { + for (IBaseCoding next : securityLabels) { + TagDefinition tag = getTag(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay()); + allDefs.add(tag); + theEntity.addTag(tag); + theEntity.setHasTags(true); + } + } + + List> profiles = theResource.getMeta().getProfile(); + if (profiles != null) { + for (IPrimitiveType next : profiles) { + TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null); + allDefs.add(tag); + theEntity.addTag(tag); + theEntity.setHasTags(true); + } + } + } + private List extractValues(String thePath, IBaseResource theResource) { List values = new ArrayList(); FhirTerser t = getContext().newTerser(); @@ -441,21 +497,6 @@ public abstract class BaseHapiFhirDao implements IDao { } } - protected void notifyInterceptors(RestOperationTypeEnum operationType, ActionRequestDetails requestDetails) { - if (requestDetails.getId() != null && requestDetails.getId().hasResourceType() && isNotBlank(requestDetails.getResourceType())) { - if (requestDetails.getId().getResourceType().equals(requestDetails.getResourceType()) == false) { - throw new InternalErrorException("Inconsistent server state - Resource types don't match: " + requestDetails.getId().getResourceType() + " / " + requestDetails.getResourceType()); - } - } - List interceptors = getConfig().getInterceptors(); - if (interceptors == null) { - return; - } - for (IServerInterceptor next : interceptors) { - next.incomingRequestPreHandled(operationType, requestDetails); - } - } - protected DaoConfig getConfig() { return myConfig; } @@ -595,7 +636,7 @@ public abstract class BaseHapiFhirDao implements IDao { @Override public List getResources(final int theFromIndex, final int theToIndex) { - final StopWatch timer = new StopWatch(); + final StopWatch grTimer = new StopWatch(); TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); return template.execute(new TransactionCallback>() { @Override @@ -604,10 +645,10 @@ public abstract class BaseHapiFhirDao implements IDao { List tupleSubList = tuples.subList(theFromIndex, theToIndex); searchHistoryCurrentVersion(tupleSubList, resEntities); - ourLog.info("Loaded history from current versions in {} ms", timer.getMillisAndRestart()); + ourLog.info("Loaded history from current versions in {} ms", grTimer.getMillisAndRestart()); searchHistoryHistory(tupleSubList, resEntities); - ourLog.info("Loaded history from previous versions in {} ms", timer.getMillisAndRestart()); + ourLog.info("Loaded history from previous versions in {} ms", grTimer.getMillisAndRestart()); Collections.sort(resEntities, new Comparator() { @Override @@ -616,9 +657,9 @@ public abstract class BaseHapiFhirDao implements IDao { } }); - int limit = theToIndex - theFromIndex; - if (resEntities.size() > limit) { - resEntities = resEntities.subList(0, limit); + int grLimit = theToIndex - theFromIndex; + if (resEntities.size() > grLimit) { + resEntities = resEntities.subList(0, grLimit); } ArrayList retVal = new ArrayList(); @@ -633,7 +674,7 @@ public abstract class BaseHapiFhirDao implements IDao { } throw e; } - IBaseResource resource = (IBaseResource) toResource(type.getImplementingClass(), next, true); + IBaseResource resource = toResource(type.getImplementingClass(), next, true); retVal.add(resource); } return retVal; @@ -642,29 +683,55 @@ public abstract class BaseHapiFhirDao implements IDao { } @Override - public int size() { - return tuples.size(); + public Integer preferredPageSize() { + return null; } @Override - public Integer preferredPageSize() { - return null; + public int size() { + return tuples.size(); } }; } - protected static boolean isValidPid(IIdType theId) { - if (theId == null || theId.getIdPart() == null) { - return false; - } - String idPart = theId.getIdPart(); - for (int i = 0; i < idPart.length(); i++) { - char nextChar = idPart.charAt(i); - if (nextChar < '0' || nextChar > '9') { - return false; + protected void notifyInterceptors(RestOperationTypeEnum operationType, ActionRequestDetails requestDetails) { + if (requestDetails.getId() != null && requestDetails.getId().hasResourceType() && isNotBlank(requestDetails.getResourceType())) { + if (requestDetails.getId().getResourceType().equals(requestDetails.getResourceType()) == false) { + throw new InternalErrorException("Inconsistent server state - Resource types don't match: " + requestDetails.getId().getResourceType() + " / " + requestDetails.getResourceType()); } } - return true; + List interceptors = getConfig().getInterceptors(); + if (interceptors == null) { + return; + } + for (IServerInterceptor next : interceptors) { + next.incomingRequestPreHandled(operationType, requestDetails); + } + } + + private String parseContentTextIntoWords(IBaseResource theResource) { + StringBuilder retVal = new StringBuilder(); + @SuppressWarnings("rawtypes") + List childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class); + for (@SuppressWarnings("rawtypes") + IPrimitiveType nextType : childElements) { + if (nextType instanceof StringDt || nextType.getClass().equals(StringType.class)) { + String nextValue = nextType.getValueAsString(); + if (isNotBlank(nextValue)) { + retVal.append(nextValue.replace("\n", " ").replace("\r", " ")); + retVal.append("\n"); + } + } + } + return retVal.toString(); + } + + private void populateResourceId(final IBaseResource theResource, BaseHasResource theEntity) { + IIdType id = theEntity.getIdDt(); + if (getContext().getVersion().getVersion().isRi()) { + id = new IdType(id.getValue()); + } + theResource.setId(id); } protected void populateResourceIntoEntity(IBaseResource theResource, ResourceTable theEntity) { @@ -729,77 +796,140 @@ public abstract class BaseHapiFhirDao implements IDao { } - private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set allDefs) { - TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource); - if (tagList != null) { - for (Tag next : tagList) { - TagDefinition tag = getTag(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel()); - allDefs.add(tag); - theEntity.addTag(tag); - theEntity.setHasTags(true); + @SuppressWarnings("unchecked") + private R populateResourceMetadataHapi(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation, IResource res) { + R retVal = (R) res; + if (theEntity.getDeleted() != null) { + res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance(); + retVal = (R) res; + ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); + if (theForHistoryOperation) { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE); + } + } else if (theForHistoryOperation) { + /* + * If the create and update times match, this was when the resource was created so we should mark it as a POST. + * Otherwise, it's a PUT. + */ + Date published = theEntity.getPublished().getValue(); + Date updated = theEntity.getUpdated().getValue(); + if (published.equals(updated)) { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST); + } else { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT); } } - List securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource); - if (securityLabels != null) { - for (BaseCodingDt next : securityLabels) { - TagDefinition tag = getTag(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue()); - allDefs.add(tag); - theEntity.addTag(tag); - theEntity.setHasTags(true); - } + res.setId(theEntity.getIdDt()); + + ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion())); + ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished()); + ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated()); + IDao.RESOURCE_PID.put(res, theEntity.getId()); + + if (theEntity.getTitle() != null) { + ResourceMetadataKeyEnum.TITLE.put(res, theEntity.getTitle()); } - List profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource); - if (profiles != null) { - for (IIdType next : profiles) { - TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null); - allDefs.add(tag); - theEntity.addTag(tag); - theEntity.setHasTags(true); + Collection tags = theEntity.getTags(); + if (theEntity.isHasTags()) { + TagList tagList = new TagList(); + List securityLabels = new ArrayList(); + List profiles = new ArrayList(); + for (BaseTag next : tags) { + switch (next.getTag().getTagType()) { + case PROFILE: + profiles.add(new IdDt(next.getTag().getCode())); + break; + case SECURITY_LABEL: + IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt(); + secLabel.setSystem(next.getTag().getSystem()); + secLabel.setCode(next.getTag().getCode()); + secLabel.setDisplay(next.getTag().getDisplay()); + securityLabels.add(secLabel); + break; + case TAG: + tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay())); + break; + } + } + if (tagList.size() > 0) { + ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList); + } + if (securityLabels.size() > 0) { + ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels)); + } + if (profiles.size() > 0) { + ResourceMetadataKeyEnum.PROFILES.put(res, profiles); } } + + return retVal; } - private void extractTagsRi(IAnyResource theResource, ResourceTable theEntity, Set allDefs) { - List tagList = theResource.getMeta().getTag(); - if (tagList != null) { - for (IBaseCoding next : tagList) { - TagDefinition tag = getTag(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay()); - allDefs.add(tag); - theEntity.addTag(tag); - theEntity.setHasTags(true); + @SuppressWarnings("unchecked") + private R populateResourceMetadataRi(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation, IAnyResource res) { + R retVal = (R) res; + if (theEntity.getDeleted() != null) { + res = (IAnyResource) myContext.getResourceDefinition(theResourceType).newInstance(); + retVal = (R) res; + ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); + if (theForHistoryOperation) { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.DELETE.toCode()); + } + } else if (theForHistoryOperation) { + /* + * If the create and update times match, this was when the resource was created so we should mark it as a POST. + * Otherwise, it's a PUT. + */ + Date published = theEntity.getPublished().getValue(); + Date updated = theEntity.getUpdated().getValue(); + if (published.equals(updated)) { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.POST.toCode()); + } else { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.PUT.toCode()); } } - List securityLabels = theResource.getMeta().getSecurity(); - if (securityLabels != null) { - for (IBaseCoding next : securityLabels) { - TagDefinition tag = getTag(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay()); - allDefs.add(tag); - theEntity.addTag(tag); - theEntity.setHasTags(true); - } - } + res.getMeta().getTag().clear(); + res.getMeta().getProfile().clear(); + res.getMeta().getSecurity().clear(); + res.getMeta().setLastUpdated(null); + res.getMeta().setVersionId(null); + + populateResourceId(res, theEntity); - List> profiles = theResource.getMeta().getProfile(); - if (profiles != null) { - for (IPrimitiveType next : profiles) { - TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null); - allDefs.add(tag); - theEntity.addTag(tag); - theEntity.setHasTags(true); + res.getMeta().setLastUpdated(theEntity.getUpdatedDate()); + IDao.RESOURCE_PID.put(res, theEntity.getId()); + + Collection tags = theEntity.getTags(); + if (theEntity.isHasTags()) { + for (BaseTag next : tags) { + switch (next.getTag().getTagType()) { + case PROFILE: + res.getMeta().addProfile(next.getTag().getCode()); + break; + case SECURITY_LABEL: + IBaseCoding sec = res.getMeta().addSecurity(); + sec.setSystem(next.getTag().getSystem()); + sec.setCode(next.getTag().getCode()); + sec.setDisplay(next.getTag().getDisplay()); + break; + case TAG: + IBaseCoding tag = res.getMeta().addTag(); + tag.setSystem(next.getTag().getSystem()); + tag.setCode(next.getTag().getCode()); + tag.setDisplay(next.getTag().getDisplay()); + break; + } } } + return retVal; } /** - * This method is called when an update to an existing resource detects that the resource supplied for update is - * missing a tag/profile/security label that the currently persisted resource holds. - *

- * The default implementation removes any profile declarations, but leaves tags and security labels in place. - * Subclasses may choose to override and change this behaviour. - *

+ * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the + * first time. * * @param theEntity * The entity being updated (Do not modify the entity! Undefined behaviour will occur!) @@ -808,35 +938,22 @@ public abstract class BaseHapiFhirDao implements IDao { * @return Returns true if the tag should be removed * @see Updates to Tags, Profiles, and Security Labels for a description of the logic that the default behaviour folows. */ - protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) { - if (theTag.getTag().getTagType() == TagTypeEnum.PROFILE) { - return true; - } - return false; + @SuppressWarnings("unused") + protected void postPersist(ResourceTable theEntity, T theResource) { + // nothing } - @CoverageIgnore - protected static IQueryParameterAnd newInstanceAnd(String chain) { - IQueryParameterAnd type; - Class> clazz = RESOURCE_META_AND_PARAMS.get(chain); - try { - type = clazz.newInstance(); - } catch (Exception e) { - throw new InternalErrorException("Failure creating instance of " + clazz, e); - } - return type; - } - - @CoverageIgnore - protected static IQueryParameterType newInstanceType(String chain) { - IQueryParameterType type; - Class clazz = RESOURCE_META_PARAMS.get(chain); - try { - type = clazz.newInstance(); - } catch (Exception e) { - throw new InternalErrorException("Failure creating instance of " + clazz, e); - } - return type; + /** + * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the + * first time. + * + * @param theEntity + * The resource + * @param theResource + * The resource being persisted + */ + protected void postUpdate(ResourceTable theEntity, T theResource) { + // nothing } protected Set processMatchUrl(String theMatchUrl, Class theResourceType) { @@ -854,100 +971,10 @@ public abstract class BaseHapiFhirDao implements IDao { return ids; } - public static SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) { - SearchParameterMap paramMap = new SearchParameterMap(); - List parameters = translateMatchUrl(theMatchUrl); - - ArrayListMultimap nameToParamLists = ArrayListMultimap.create(); - for (NameValuePair next : parameters) { - if (isBlank(next.getValue())) { - continue; - } - - String paramName = next.getName(); - String qualifier = null; - for (int i = 0; i < paramName.length(); i++) { - switch (paramName.charAt(i)) { - case '.': - case ':': - qualifier = paramName.substring(i); - paramName = paramName.substring(0, i); - i = Integer.MAX_VALUE - 1; - break; - } - } - - QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue()); - nameToParamLists.put(paramName, paramList); - } - - for (String nextParamName : nameToParamLists.keySet()) { - List paramList = nameToParamLists.get(nextParamName); - if (Constants.PARAM_LASTUPDATED.equals(nextParamName)) { - if (paramList != null && paramList.size() > 0) { - if (paramList.size() > 2) { - throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Can not have more than 2 " + Constants.PARAM_LASTUPDATED + " parameter repetitions"); - } else { - DateRangeParam p1 = new DateRangeParam(); - p1.setValuesAsQueryTokens(paramList); - paramMap.setLastUpdated(p1); - } - } - continue; - } - - if (Constants.PARAM_COUNT.equals(nextParamName)) { - if (paramList.size() > 0 && paramList.get(0).size() > 0) { - String intString = paramList.get(0).get(0); - try { - paramMap.setCount(Integer.parseInt(intString)); - } catch (NumberFormatException e) { - throw new InvalidRequestException("Invalid " + Constants.PARAM_COUNT + " value: " + intString); - } - } - continue; - } - - if (RESOURCE_META_PARAMS.containsKey(nextParamName)) { - if (isNotBlank(paramList.get(0).getQualifier()) && paramList.get(0).getQualifier().startsWith(".")) { - throw new InvalidRequestException("Invalid parameter chain: " + nextParamName + paramList.get(0).getQualifier()); - } - IQueryParameterAnd type = newInstanceAnd(nextParamName); - type.setValuesAsQueryTokens((paramList)); - paramMap.add(nextParamName, type); - } else if (nextParamName.startsWith("_")) { - // ignore these since they aren't search params (e.g. _sort) - } else { - RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); - if (paramDef == null) { - throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); - } - - IQueryParameterAnd param = MethodUtil.parseQueryParams(paramDef, nextParamName, paramList); - paramMap.add(nextParamName, param); - } - } - return paramMap; - } - - protected static List translateMatchUrl(String theMatchUrl) { - List parameters; - String matchUrl = theMatchUrl; - int questionMarkIndex = matchUrl.indexOf('?'); - if (questionMarkIndex != -1) { - matchUrl = matchUrl.substring(questionMarkIndex + 1); - } - matchUrl = matchUrl.replace("|", "%7C"); - matchUrl = matchUrl.replace("=>=", "=%3E%3D"); - matchUrl = matchUrl.replace("=<=", "=%3C%3D"); - matchUrl = matchUrl.replace("=>", "=%3E"); - matchUrl = matchUrl.replace("=<", "=%3C"); - if (matchUrl.contains(" ")) { - throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)"); - } - - parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&'); - return parameters; + @SuppressWarnings("unused") + @CoverageIgnore + public BaseHasResource readEntity(IIdType theValueId) { + throw new NotImplementedException(""); } private void searchHistoryCurrentVersion(List theTuples, List theRetVal) { @@ -1086,6 +1113,10 @@ public abstract class BaseHapiFhirDao implements IDao { } } + public void setConfig(DaoConfig theConfig) { + myConfig = theConfig; + } + // protected MetaDt toMetaDt(Collection tagDefinitions) { // MetaDt retVal = new MetaDt(); // for (TagDefinition next : tagDefinitions) { @@ -1122,6 +1153,37 @@ public abstract class BaseHapiFhirDao implements IDao { } } + public void setEntityManager(EntityManager theEntityManager) { + myEntityManager = theEntityManager; + } + + public void setPlatformTransactionManager(PlatformTransactionManager thePlatformTransactionManager) { + myPlatformTransactionManager = thePlatformTransactionManager; + } + + /** + * This method is called when an update to an existing resource detects that the resource supplied for update is + * missing a tag/profile/security label that the currently persisted resource holds. + *

+ * The default implementation removes any profile declarations, but leaves tags and security labels in place. + * Subclasses may choose to override and change this behaviour. + *

+ * + * @param theEntity + * The entity being updated (Do not modify the entity! Undefined behaviour will occur!) + * @param theTag + * The tag + * @return Retturns true if the tag should be removed + * @see Updates to Tags, Profiles, and Security + * Labels for a description of the logic that the default behaviour folows. + */ + protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) { + if (theTag.getTag().getTagType() == TagTypeEnum.PROFILE) { + return true; + } + return false; + } + protected ResourceTable toEntity(IResource theResource) { ResourceTable retVal = new ResourceTable(); @@ -1136,191 +1198,53 @@ public abstract class BaseHapiFhirDao implements IDao { return toResource(type.getImplementingClass(), theEntity, theForHistoryOperation); } - @Override - @SuppressWarnings("unchecked") - public R toResource(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) { - String resourceText = null; - switch (theEntity.getEncoding()) { - case JSON: + @Override + public R toResource(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) { + String resourceText = null; + switch (theEntity.getEncoding()) { + case JSON: + try { + resourceText = new String(theEntity.getResource(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new Error("Should not happen", e); + } + break; + case JSONC: + resourceText = GZipUtil.decompress(theEntity.getResource()); + break; + } + + IParser parser = theEntity.getEncoding().newParser(getContext(theEntity.getFhirVersion())); + R retVal; try { - resourceText = new String(theEntity.getResource(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new Error("Should not happen", e); + retVal = parser.parseResource(theResourceType, resourceText); + } catch (Exception e) { + StringBuilder b = new StringBuilder(); + b.append("Failed to parse database resource["); + b.append(theResourceType); + b.append("/"); + b.append(theEntity.getIdDt().getIdPart()); + b.append(" (pid "); + b.append(theEntity.getId()); + b.append(", version "); + b.append(myContext.getVersion().getVersion()); + b.append("): "); + b.append(e.getMessage()); + String msg = b.toString(); + ourLog.error(msg, e); + throw new DataFormatException(msg, e); } - break; - case JSONC: - resourceText = GZipUtil.decompress(theEntity.getResource()); - break; - } - IParser parser = theEntity.getEncoding().newParser(getContext(theEntity.getFhirVersion())); - R retVal; - try { - retVal = parser.parseResource(theResourceType, resourceText); - } catch (Exception e) { - StringBuilder b = new StringBuilder(); - b.append("Failed to parse database resource["); - b.append(theResourceType); - b.append("/"); - b.append(theEntity.getIdDt().getIdPart()); - b.append(" (pid "); - b.append(theEntity.getId()); - b.append(", version "); - b.append(myContext.getVersion().getVersion()); - b.append("): "); - b.append(e.getMessage()); - String msg = b.toString(); - ourLog.error(msg, e); - throw new DataFormatException(msg, e); - } - - if (retVal instanceof IResource) { - IResource res = (IResource) retVal; - retVal = populateResourceMetadataHapi(theResourceType, theEntity, theForHistoryOperation, res); - } else { - IAnyResource res = (IAnyResource) retVal; - retVal = populateResourceMetadataRi(theResourceType, theEntity, theForHistoryOperation, res); - } - return retVal; - } - - private R populateResourceMetadataHapi(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation, IResource res) { - R retVal = (R) res; - if (theEntity.getDeleted() != null) { - res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance(); - retVal = (R) res; - ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); - if (theForHistoryOperation) { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE); - } - } else if (theForHistoryOperation) { - /* - * If the create and update times match, this was when the resource was created so we should mark it as a POST. - * Otherwise, it's a PUT. - */ - Date published = theEntity.getPublished().getValue(); - Date updated = theEntity.getUpdated().getValue(); - if (published.equals(updated)) { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST); + if (retVal instanceof IResource) { + IResource res = (IResource) retVal; + retVal = populateResourceMetadataHapi(theResourceType, theEntity, theForHistoryOperation, res); } else { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT); + IAnyResource res = (IAnyResource) retVal; + retVal = populateResourceMetadataRi(theResourceType, theEntity, theForHistoryOperation, res); } + return retVal; } - res.setId(theEntity.getIdDt()); - - ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion())); - ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished()); - ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated()); - IDao.RESOURCE_PID.put(res, theEntity.getId()); - - if (theEntity.getTitle() != null) { - ResourceMetadataKeyEnum.TITLE.put(res, theEntity.getTitle()); - } - - Collection tags = theEntity.getTags(); - if (theEntity.isHasTags()) { - TagList tagList = new TagList(); - List securityLabels = new ArrayList(); - List profiles = new ArrayList(); - for (BaseTag next : tags) { - switch (next.getTag().getTagType()) { - case PROFILE: - profiles.add(new IdDt(next.getTag().getCode())); - break; - case SECURITY_LABEL: - IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt(); - secLabel.setSystem(next.getTag().getSystem()); - secLabel.setCode(next.getTag().getCode()); - secLabel.setDisplay(next.getTag().getDisplay()); - securityLabels.add(secLabel); - break; - case TAG: - tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay())); - break; - } - } - if (tagList.size() > 0) { - ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList); - } - if (securityLabels.size() > 0) { - ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels)); - } - if (profiles.size() > 0) { - ResourceMetadataKeyEnum.PROFILES.put(res, profiles); - } - } - - return retVal; - } - - private R populateResourceMetadataRi(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation, IAnyResource res) { - R retVal = (R) res; - if (theEntity.getDeleted() != null) { - res = (IAnyResource) myContext.getResourceDefinition(theResourceType).newInstance(); - retVal = (R) res; - ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); - if (theForHistoryOperation) { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.DELETE.toCode()); - } - } else if (theForHistoryOperation) { - /* - * If the create and update times match, this was when the resource was created so we should mark it as a POST. - * Otherwise, it's a PUT. - */ - Date published = theEntity.getPublished().getValue(); - Date updated = theEntity.getUpdated().getValue(); - if (published.equals(updated)) { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.POST.toCode()); - } else { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.PUT.toCode()); - } - } - - res.getMeta().getTag().clear(); - res.getMeta().getProfile().clear(); - res.getMeta().getSecurity().clear(); - res.getMeta().setLastUpdated(null); - res.getMeta().setVersionId(null); - - populateResourceId(res, theEntity); - - res.getMeta().setLastUpdated(theEntity.getUpdatedDate()); - IDao.RESOURCE_PID.put(res, theEntity.getId()); - - Collection tags = theEntity.getTags(); - if (theEntity.isHasTags()) { - for (BaseTag next : tags) { - switch (next.getTag().getTagType()) { - case PROFILE: - res.getMeta().addProfile(next.getTag().getCode()); - break; - case SECURITY_LABEL: - IBaseCoding sec = res.getMeta().addSecurity(); - sec.setSystem(next.getTag().getSystem()); - sec.setCode(next.getTag().getCode()); - sec.setDisplay(next.getTag().getDisplay()); - break; - case TAG: - IBaseCoding tag = res.getMeta().addTag(); - tag.setSystem(next.getTag().getSystem()); - tag.setCode(next.getTag().getCode()); - tag.setDisplay(next.getTag().getDisplay()); - break; - } - } - } - return retVal; - } - - private static List toBaseCodingList(List theSecurityLabels) { - ArrayList retVal = new ArrayList(theSecurityLabels.size()); - for (IBaseCoding next : theSecurityLabels) { - retVal.add((BaseCodingDt) next); - } - return retVal; - } - protected String toResourceName(Class theResourceType) { return myContext.getResourceDefinition(theResourceType).getName(); } @@ -1333,28 +1257,8 @@ public abstract class BaseHapiFhirDao implements IDao { return translateForcedIdToPid(theId, myEntityManager); } - public static void validateResourceType(BaseHasResource theEntity, String theResourceName) { - if (!theResourceName.equals(theEntity.getResourceType())) { - throw new ResourceNotFoundException("Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType()); - } - } - - static Long translateForcedIdToPid(IIdType theId, EntityManager entityManager) { - if (isValidPid(theId)) { - return theId.getIdPartAsLong(); - } else { - TypedQuery q = entityManager.createNamedQuery("Q_GET_FORCED_ID", ForcedId.class); - q.setParameter("ID", theId.getIdPart()); - try { - return q.getSingleResult().getResourcePid(); - } catch (NoResultException e) { - throw new ResourceNotFoundException(theId); - } - } - } - protected String translatePidIdToForcedId(Long theId) { - ForcedId forcedId = myEntityManager.find(ForcedId.class, theId); + ForcedId forcedId = myForcedIdDao.findByResourcePid(theId); if (forcedId != null) { return forcedId.getForcedId(); } else { @@ -1477,6 +1381,49 @@ public abstract class BaseHapiFhirDao implements IDao { } } + /* + * Handle references within the resource that are match URLs, for example + * references like "Patient?identifier=foo". These match URLs are resolved + * and replaced with the ID of the matching resource. + */ + if (myConfig.isAllowInlineMatchUrlReferences()) { + FhirTerser terser = getContext().newTerser(); + List allRefs = terser.getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); + for (IBaseReference nextRef : allRefs) { + IIdType nextId = nextRef.getReferenceElement(); + String nextIdText = nextId.getValue(); + int qmIndex = nextIdText.indexOf('?'); + if (qmIndex != -1) { + for (int i = qmIndex - 1; i >= 0; i--) { + if (nextIdText.charAt(i) == '/') { + nextIdText = nextIdText.substring(i + 1); + break; + } + } + String resourceTypeString = nextIdText.substring(0, nextIdText.indexOf('?')); + RuntimeResourceDefinition matchResourceDef = getContext().getResourceDefinition(resourceTypeString); + if (matchResourceDef == null) { + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlInvalidResourceType", nextId.getValue(), resourceTypeString); + throw new InvalidRequestException(msg); + } + Class matchResourceType = matchResourceDef.getImplementingClass(); + Set matches = processMatchUrl(nextIdText, matchResourceType); + if (matches.isEmpty()) { + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue()); + throw new InvalidRequestException(msg); + } + if (matches.size() > 1) { + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue()); + throw new InvalidRequestException(msg); + } + Long next = matches.iterator().next(); + String newId = resourceTypeString + '/' + translatePidIdToForcedId(next); + ourLog.info("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId); + nextRef.setReference(newId); + } + } + } + links = extractResourceLinks(theEntity, theResource); /* @@ -1620,40 +1567,21 @@ public abstract class BaseHapiFhirDao implements IDao { return theEntity; } - private void populateResourceId(final IBaseResource theResource, BaseHasResource theEntity) { - IIdType id = theEntity.getIdDt(); - if (getContext().getVersion().getVersion().isRi()) { - id = new IdType(id.getValue()); + protected void validateDeleteConflictsEmptyOrThrowException(List theDeleteConflicts) { + if (theDeleteConflicts.isEmpty()) { + return; } - theResource.setId(id); - } - /** - * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the - * first time. - * - * @param theEntity - * The resource - * @param theResource - * The resource being persisted - */ - protected void postUpdate(ResourceTable theEntity, T theResource) { - // nothing - } + IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); + for (DeleteConflict next : theDeleteConflicts) { + String msg = "Unable to delete " + next.getTargetId().toUnqualifiedVersionless().getValue() + " because at least one resource has a reference to this resource. First reference found was resource " + next.getTargetId().toUnqualifiedVersionless().getValue() + " in path " + + next.getSourcePath(); + OperationOutcomeUtil.addIssue(getContext(), oo, OO_SEVERITY_ERROR, msg, null, "processing"); + } - /** - * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the - * first time. - * - * @param theEntity - * The resource - * @param theResource - * The resource being persisted - */ - protected void postPersist(ResourceTable theEntity, T theResource) { - // nothing + throw new ResourceVersionConflictException("Delete failed because of constraint failure", oo); } - + /** * This method is invoked immediately before storing a new resource, or an update to an existing resource to allow * the DAO to ensure that it is valid for persistence. By default, checks for the "subsetted" tag and rejects @@ -1683,6 +1611,44 @@ public abstract class BaseHapiFhirDao implements IDao { } } + protected static boolean isValidPid(IIdType theId) { + if (theId == null || theId.getIdPart() == null) { + return false; + } + String idPart = theId.getIdPart(); + for (int i = 0; i < idPart.length(); i++) { + char nextChar = idPart.charAt(i); + if (nextChar < '0' || nextChar > '9') { + return false; + } + } + return true; + } + + @CoverageIgnore + protected static IQueryParameterAnd newInstanceAnd(String chain) { + IQueryParameterAnd type; + Class> clazz = RESOURCE_META_AND_PARAMS.get(chain); + try { + type = clazz.newInstance(); + } catch (Exception e) { + throw new InternalErrorException("Failure creating instance of " + clazz, e); + } + return type; + } + + @CoverageIgnore + protected static IQueryParameterType newInstanceType(String chain) { + IQueryParameterType type; + Class clazz = RESOURCE_META_PARAMS.get(chain); + try { + type = clazz.newInstance(); + } catch (Exception e) { + throw new InternalErrorException("Failure creating instance of " + clazz, e); + } + return type; + } + public static String normalizeString(String theString) { char[] out = new char[theString.length()]; theString = Normalizer.normalize(theString, Normalizer.Form.NFD); @@ -1732,40 +1698,128 @@ public abstract class BaseHapiFhirDao implements IDao { return b.toString(); } - private String parseContentTextIntoWords(IBaseResource theResource) { - StringBuilder retVal = new StringBuilder(); - @SuppressWarnings("rawtypes") - List childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class); - for (@SuppressWarnings("rawtypes") - IPrimitiveType nextType : childElements) { - if (nextType instanceof StringDt || nextType.getClass().equals(StringType.class)) { - String nextValue = nextType.getValueAsString(); - if (isNotBlank(nextValue)) { - retVal.append(nextValue.replace("\n", " ").replace("\r", " ")); - retVal.append("\n"); - } + private static List toBaseCodingList(List theSecurityLabels) { + ArrayList retVal = new ArrayList(theSecurityLabels.size()); + for (IBaseCoding next : theSecurityLabels) { + retVal.add((BaseCodingDt) next); + } + return retVal; +} + + static Long translateForcedIdToPid(IIdType theId, EntityManager entityManager) { + if (isValidPid(theId)) { + return theId.getIdPartAsLong(); + } else { + TypedQuery q = entityManager.createNamedQuery("Q_GET_FORCED_ID", ForcedId.class); + q.setParameter("ID", theId.getIdPart()); + try { + return q.getSingleResult().getResourcePid(); + } catch (NoResultException e) { + throw new ResourceNotFoundException(theId); } } - return retVal.toString(); } - protected void validateDeleteConflictsEmptyOrThrowException(List theDeleteConflicts) { - if (theDeleteConflicts.isEmpty()) { - return; + protected static List translateMatchUrl(String theMatchUrl) { + List parameters; + String matchUrl = theMatchUrl; + int questionMarkIndex = matchUrl.indexOf('?'); + if (questionMarkIndex != -1) { + matchUrl = matchUrl.substring(questionMarkIndex + 1); + } + matchUrl = matchUrl.replace("|", "%7C"); + matchUrl = matchUrl.replace("=>=", "=%3E%3D"); + matchUrl = matchUrl.replace("=<=", "=%3C%3D"); + matchUrl = matchUrl.replace("=>", "=%3E"); + matchUrl = matchUrl.replace("=<", "=%3C"); + if (matchUrl.contains(" ")) { + throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)"); } - IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); - for (DeleteConflict next : theDeleteConflicts) { - String msg = "Unable to delete " + next.getTargetId().toUnqualifiedVersionless().getValue() + " because at least one resource has a reference to this resource. First reference found was resource " + next.getTargetId().toUnqualifiedVersionless().getValue() + " in path " - + next.getSourcePath(); - OperationOutcomeUtil.addIssue(getContext(), oo, OO_SEVERITY_ERROR, msg, null, "processing"); - } - - throw new ResourceVersionConflictException("Delete failed because of constraint failure", oo); + parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&'); + return parameters; } - public BaseHasResource readEntity(IIdType theValueId) { - throw new NotImplementedException(""); + public static SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) { + SearchParameterMap paramMap = new SearchParameterMap(); + List parameters = translateMatchUrl(theMatchUrl); + + ArrayListMultimap nameToParamLists = ArrayListMultimap.create(); + for (NameValuePair next : parameters) { + if (isBlank(next.getValue())) { + continue; + } + + String paramName = next.getName(); + String qualifier = null; + for (int i = 0; i < paramName.length(); i++) { + switch (paramName.charAt(i)) { + case '.': + case ':': + qualifier = paramName.substring(i); + paramName = paramName.substring(0, i); + i = Integer.MAX_VALUE - 1; + break; + } + } + + QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue()); + nameToParamLists.put(paramName, paramList); + } + + for (String nextParamName : nameToParamLists.keySet()) { + List paramList = nameToParamLists.get(nextParamName); + if (Constants.PARAM_LASTUPDATED.equals(nextParamName)) { + if (paramList != null && paramList.size() > 0) { + if (paramList.size() > 2) { + throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Can not have more than 2 " + Constants.PARAM_LASTUPDATED + " parameter repetitions"); + } else { + DateRangeParam p1 = new DateRangeParam(); + p1.setValuesAsQueryTokens(paramList); + paramMap.setLastUpdated(p1); + } + } + continue; + } + + if (Constants.PARAM_COUNT.equals(nextParamName)) { + if (paramList.size() > 0 && paramList.get(0).size() > 0) { + String intString = paramList.get(0).get(0); + try { + paramMap.setCount(Integer.parseInt(intString)); + } catch (NumberFormatException e) { + throw new InvalidRequestException("Invalid " + Constants.PARAM_COUNT + " value: " + intString); + } + } + continue; + } + + if (RESOURCE_META_PARAMS.containsKey(nextParamName)) { + if (isNotBlank(paramList.get(0).getQualifier()) && paramList.get(0).getQualifier().startsWith(".")) { + throw new InvalidRequestException("Invalid parameter chain: " + nextParamName + paramList.get(0).getQualifier()); + } + IQueryParameterAnd type = newInstanceAnd(nextParamName); + type.setValuesAsQueryTokens((paramList)); + paramMap.add(nextParamName, type); + } else if (nextParamName.startsWith("_")) { + // ignore these since they aren't search params (e.g. _sort) + } else { + RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); + if (paramDef == null) { + throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); + } + + IQueryParameterAnd param = MethodUtil.parseQueryParams(paramDef, nextParamName, paramList); + paramMap.add(nextParamName, param); + } + } + return paramMap; + } + + public static void validateResourceType(BaseHasResource theEntity, String theResourceName) { + if (!theResourceName.equals(theEntity.getResourceType())) { + throw new ResourceNotFoundException("Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType()); + } } } 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 1bec9df1921..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 @@ -34,32 +34,39 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; public class DaoConfig { - private boolean myAllowMultipleDelete; + // *** + // update setter javadoc if default changes + // *** + private boolean myAllowInlineMatchUrlReferences = false; + + 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. * @@ -84,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; } @@ -99,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; } @@ -120,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/dao/data/IForcedIdDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java new file mode 100644 index 00000000000..5abd3907349 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import ca.uhn.fhir.jpa.entity.ForcedId; + +public interface IForcedIdDao extends JpaRepository { + + @Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid") + public ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java index 2e49a1d6f26..50a8a37ee7a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java @@ -462,7 +462,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { FhirTerser terser = getContext().newTerser(); for (DaoMethodOutcome nextOutcome : idToPersistedOutcome.values()) { - IBaseResource nextResource = (IBaseResource) nextOutcome.getResource(); + IBaseResource nextResource = nextOutcome.getResource(); if (nextResource == null) { continue; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java index 2653f61afb1..e21fb56ef32 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java @@ -35,7 +35,8 @@ import javax.persistence.UniqueConstraint; //@formatter:off @Entity() @Table(name = "HFJ_FORCED_ID", uniqueConstraints = { - @UniqueConstraint(name = "IDX_FORCEDID", columnNames = {"FORCED_ID"}) + @UniqueConstraint(name = "IDX_FORCEDID", columnNames = {"FORCED_ID"}), + @UniqueConstraint(name = "IDX_FORCEDID_RESID", columnNames = {"RESOURCE_PID"}) }) @NamedQueries(value = { @NamedQuery(name = "Q_GET_FORCED_ID", query = "SELECT f FROM ForcedId f WHERE myForcedId = :ID") 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/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 14261c933a2..c4e36e56d99 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -1,8 +1,9 @@ package ca.uhn.fhir.jpa.dao; +import static org.mockito.Mockito.mock; + import java.io.IOException; import java.io.InputStream; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -12,21 +13,29 @@ import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; +import org.junit.Before; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; public class BaseJpaTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseJpaTest.class); + protected ServletRequestDetails mySrd; @SuppressWarnings({ "rawtypes" }) protected List toList(IBundleProvider theSearch) { return theSearch.getResources(0, theSearch.size()); } + @Before + public void beforeCreateSrd() { + mySrd = mock(ServletRequestDetails.class); + } + protected List toUnqualifiedVersionlessIds(Bundle theFound) { List retVal = new ArrayList(); for (Entry next : theFound.getEntry()) { 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 ffb25854525..78707accb4e 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 @@ -47,6 +47,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; @@ -239,14 +240,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..bc8cd77dd02 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ContainedTest.java @@ -0,0 +1,59 @@ +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; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; + +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, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.getCode().setText("Some Observation"); + o2.setSubject(new Reference(p)); + IIdType oid2 = myObservationDao.create(o2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + + Patient p2 = new Patient(); + p2.addName().addFamily("MYFAMILY").addGiven("MYGIVEN"); + IIdType pid2 = myPatientDao.create(p2, new ServletRequestDetails()).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 508da9b26b9..33eff79c03c 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; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -46,31 +47,35 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { obs1.setStatus(ObservationStatus.FINAL); obs1.setValue(new Quantity(123)); obs1.setComments("obs1"); - IIdType id1 = myObservationDao.create(obs1, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType id1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless(); Observation obs2 = new Observation(); obs2.getCode().setText("Diastolic Blood Pressure"); obs2.setStatus(ObservationStatus.FINAL); obs2.setValue(new Quantity(81)); - IIdType id2 = myObservationDao.create(obs2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType id2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); 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))); } + private ServletRequestDetails mockSrd() { + return new ServletRequestDetails(); + } + @Test @Ignore public void testStringTextSearch() { @@ -78,13 +83,13 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { obs1.getCode().setText("AAAAA"); obs1.setValue(new StringType("Systolic Blood Pressure")); obs1.setStatus(ObservationStatus.FINAL); - IIdType id1 = myObservationDao.create(obs1, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType id1 = myObservationDao.create(obs1, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs2 = new Observation(); obs1.getCode().setText("AAAAA"); obs1.setValue(new StringType("Diastolic Blood Pressure")); obs2.setStatus(ObservationStatus.FINAL); - IIdType id2 = myObservationDao.create(obs2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType id2 = myObservationDao.create(obs2, mockSrd()).getId().toUnqualifiedVersionless(); SearchParameterMap map; @@ -99,7 +104,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { public void testSuggestIgnoresBase64Content() { Patient patient = new Patient(); patient.addName().addFamily("testSuggest"); - IIdType ptId = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType ptId = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); Media med = new Media(); med.getSubject().setReferenceElement(ptId); @@ -107,7 +112,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { med.getContent().setContentType("LCws"); med.getContent().setDataElement(new Base64BinaryType(new byte[] {44,44,44,44,44,44,44,44})); med.getContent().setTitle("bbbb syst"); - myMediaDao.create(med, new ServletRequestDetails()); + myMediaDao.create(med, mockSrd()); ourLog.info(myFhirCtx.newJsonParser().encodeResourceToString(med)); List output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "press"); @@ -139,35 +144,35 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { public void testSuggest() { Patient patient = new Patient(); patient.addName().addFamily("testSuggest"); - IIdType ptId = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType ptId = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs = new Observation(); obs.getSubject().setReferenceElement(ptId); obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL"); - myObservationDao.create(obs, new ServletRequestDetails()); + myObservationDao.create(obs, mockSrd()); obs = new Observation(); obs.getSubject().setReferenceElement(ptId); obs.getCode().setText("MNBVCXZ"); - myObservationDao.create(obs, new ServletRequestDetails()); + myObservationDao.create(obs, mockSrd()); obs = new Observation(); obs.getSubject().setReferenceElement(ptId); obs.getCode().setText("ZXC HELLO"); obs.addComponent().getCode().setText("HHHHHHHHHH"); - myObservationDao.create(obs, new ServletRequestDetails()); + myObservationDao.create(obs, mockSrd()); /* * These shouldn't match since they're for another patient */ patient = new Patient(); patient.addName().addFamily("testSuggest2"); - IIdType ptId2 = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType ptId2 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs2 = new Observation(); obs2.getSubject().setReferenceElement(ptId2); obs2.getCode().setText("ZXCVBNMZZ"); - myObservationDao.create(obs2, new ServletRequestDetails()); + myObservationDao.create(obs2, mockSrd()); List output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXCVBNM"); ourLog.info("Found: " + output); @@ -212,7 +217,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { patient = new Patient(); patient.getText().setDivAsString("

DIVAAA
"); patient.addName().addGiven("NAMEAAA"); - IIdType pId1 = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType pId1 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); map = new SearchParameterMap(); map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); @@ -230,7 +235,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { patient.setId(pId1); patient.getText().setDivAsString("
DIVBBB
"); patient.addName().addGiven("NAMEBBB"); - myPatientDao.update(patient, new ServletRequestDetails()); + myPatientDao.update(patient, mockSrd()); map = new SearchParameterMap(); map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); @@ -254,19 +259,19 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { public void testEverythingInstanceWithContentFilter() { Patient pt1 = new Patient(); pt1.addName().addFamily("Everything").addGiven("Arthur"); - IIdType ptId1 = myPatientDao.create(pt1, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType ptId1 = myPatientDao.create(pt1, mockSrd()).getId().toUnqualifiedVersionless(); Patient pt2 = new Patient(); pt2.addName().addFamily("Everything").addGiven("Arthur"); - IIdType ptId2 = myPatientDao.create(pt2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType ptId2 = myPatientDao.create(pt2, mockSrd()).getId().toUnqualifiedVersionless(); Device dev1 = new Device(); dev1.setManufacturer("Some Manufacturer"); - IIdType devId1 = myDeviceDao.create(dev1, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType devId1 = myDeviceDao.create(dev1, mockSrd()).getId().toUnqualifiedVersionless(); Device dev2 = new Device(); dev2.setManufacturer("Some Manufacturer 2"); - myDeviceDao.create(dev2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + myDeviceDao.create(dev2, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs1 = new Observation(); obs1.getText().setDivAsString("
OBSTEXT1
"); @@ -274,19 +279,19 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { obs1.getCode().addCoding().setCode("CODE1"); obs1.setValue(new StringType("obsvalue1")); obs1.getDevice().setReferenceElement(devId1); - IIdType obsId1 = myObservationDao.create(obs1, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType obsId1 = myObservationDao.create(obs1, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs2 = new Observation(); obs2.getSubject().setReferenceElement(ptId1); obs2.getCode().addCoding().setCode("CODE2"); obs2.setValue(new StringType("obsvalue2")); - IIdType obsId2 = myObservationDao.create(obs2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType obsId2 = myObservationDao.create(obs2, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs3 = new Observation(); obs3.getSubject().setReferenceElement(ptId2); obs3.getCode().addCoding().setCode("CODE3"); obs3.setValue(new StringType("obsvalue3")); - IIdType obsId3 = myObservationDao.create(obs3, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType obsId3 = myObservationDao.create(obs3, mockSrd()).getId().toUnqualifiedVersionless(); HttpServletRequest request; List actual; @@ -297,16 +302,16 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obstext1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); request = mock(HttpServletRequest.class); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1))); /* @@ -317,12 +322,12 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { obs4.getSubject().setReferenceElement(ptId1); obs4.getCode().addCoding().setCode("CODE1"); obs4.setValue(new StringType("obsvalue1")); - IIdType obsId4 = myObservationDao.create(obs4, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType obsId4 = myObservationDao.create(obs4, mockSrd()).getId().toUnqualifiedVersionless(); assertNotEquals(obsId1.getIdPart(), obsId4.getIdPart(), devId1); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1))); /* @@ -334,11 +339,11 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { obs1.getSubject().setReferenceElement(ptId1); obs1.getCode().addCoding().setCode("CODE2"); obs1.setValue(new StringType("obsvalue2")); - myObservationDao.update(obs1, new ServletRequestDetails()); + myObservationDao.update(obs1, mockSrd()); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4))); } @@ -347,38 +352,38 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { public void testEverythingTypeWithContentFilter() { Patient pt1 = new Patient(); pt1.addName().addFamily("Everything").addGiven("Arthur"); - IIdType ptId1 = myPatientDao.create(pt1, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType ptId1 = myPatientDao.create(pt1, mockSrd()).getId().toUnqualifiedVersionless(); Patient pt2 = new Patient(); pt2.addName().addFamily("Everything").addGiven("Arthur"); - IIdType ptId2 = myPatientDao.create(pt2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType ptId2 = myPatientDao.create(pt2, mockSrd()).getId().toUnqualifiedVersionless(); Device dev1 = new Device(); dev1.setManufacturer("Some Manufacturer"); - IIdType devId1 = myDeviceDao.create(dev1, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType devId1 = myDeviceDao.create(dev1, mockSrd()).getId().toUnqualifiedVersionless(); Device dev2 = new Device(); dev2.setManufacturer("Some Manufacturer 2"); - IIdType devId2 = myDeviceDao.create(dev2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + myDeviceDao.create(dev2, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs1 = new Observation(); obs1.getSubject().setReferenceElement(ptId1); obs1.getCode().addCoding().setCode("CODE1"); obs1.setValue(new StringType("obsvalue1")); obs1.getDevice().setReferenceElement(devId1); - IIdType obsId1 = myObservationDao.create(obs1, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType obsId1 = myObservationDao.create(obs1, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs2 = new Observation(); obs2.getSubject().setReferenceElement(ptId1); obs2.getCode().addCoding().setCode("CODE2"); obs2.setValue(new StringType("obsvalue2")); - IIdType obsId2 = myObservationDao.create(obs2, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType obsId2 = myObservationDao.create(obs2, mockSrd()).getId().toUnqualifiedVersionless(); Observation obs3 = new Observation(); obs3.getSubject().setReferenceElement(ptId2); obs3.getCode().addCoding().setCode("CODE3"); obs3.setValue(new StringType("obsvalue3")); - IIdType obsId3 = myObservationDao.create(obs3, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType obsId3 = myObservationDao.create(obs3, mockSrd()).getId().toUnqualifiedVersionless(); HttpServletRequest request; List actual; @@ -389,11 +394,11 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); request = mock(HttpServletRequest.class); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1, ptId2, obsId3))); /* @@ -404,12 +409,12 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { obs4.getSubject().setReferenceElement(ptId1); obs4.getCode().addCoding().setCode("CODE1"); obs4.setValue(new StringType("obsvalue1")); - IIdType obsId4 = myObservationDao.create(obs4, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType obsId4 = myObservationDao.create(obs4, mockSrd()).getId().toUnqualifiedVersionless(); assertNotEquals(obsId1.getIdPart(), obsId4.getIdPart(), devId1); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1))); /* @@ -421,11 +426,11 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { obs1.getSubject().setReferenceElement(ptId1); obs1.getCode().addCoding().setCode("CODE2"); obs1.setValue(new StringType("obsvalue2")); - myObservationDao.update(obs1, new ServletRequestDetails()); + myObservationDao.update(obs1, mockSrd()); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, new ServletRequestDetails())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4))); } @@ -443,7 +448,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { patient = new Patient(); patient.getText().setDivAsString("
DIVAAA
"); patient.addName().addGiven("NAMEAAA"); - IIdType pId1 = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType pId1 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); map = new SearchParameterMap(); map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); @@ -461,7 +466,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { patient.setId(pId1); patient.getText().setDivAsString("
DIVBBB
"); patient.addName().addGiven("NAMEBBB"); - myPatientDao.update(patient, null, false, new ServletRequestDetails()); + myPatientDao.update(patient, null, false, mockSrd()); map = new SearchParameterMap(); map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); @@ -470,7 +475,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { map.add(Constants.PARAM_CONTENT, new StringParam("NAMEBBB")); assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), not(contains(toValues(pId1)))); - myPatientDao.update(patient, null, true, new ServletRequestDetails()); + myPatientDao.update(patient, null, true, mockSrd()); map = new SearchParameterMap(); map.add(Constants.PARAM_CONTENT, new StringParam("NAMEAAA")); @@ -498,18 +503,18 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { Patient patient = new Patient(); patient.addName().addGiven(methodName); patient.addAddress().addLine("My fulltext address"); - pId1 = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + pId1 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless(); } Observation obs = new Observation(); obs.getSubject().setReferenceElement(pId1); obs.setValue(new StringType("This is the FULLtext of the observation")); - IIdType oId1 = myObservationDao.create(obs, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType oId1 = myObservationDao.create(obs, mockSrd()).getId().toUnqualifiedVersionless(); obs = new Observation(); obs.getSubject().setReferenceElement(pId1); obs.setValue(new StringType("Another fullText")); - IIdType oId2 = myObservationDao.create(obs, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + IIdType oId2 = myObservationDao.create(obs, mockSrd()).getId().toUnqualifiedVersionless(); List patients; SearchParameterMap params; 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 e56a22b1a9c..1c3df054e74 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,7 +23,6 @@ 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; @@ -52,6 +51,7 @@ 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.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.Ignore; @@ -586,21 +586,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()); } @@ -629,18 +629,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)); } { @@ -648,7 +648,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)); } { @@ -656,7 +656,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()); } { @@ -664,7 +664,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()); } { @@ -672,7 +672,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)); } { @@ -681,7 +681,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)); } { @@ -689,7 +689,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)); } @@ -767,8 +767,6 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { @Test public void testSearchLastUpdatedParamWithComparator() throws InterruptedException { - String methodName = "testSearchLastUpdatedParamWithComparator"; - IIdType id0; { Patient patient = new Patient(); @@ -781,7 +779,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(); @@ -815,12 +812,12 @@ 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)); @@ -1373,7 +1370,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(); @@ -1384,19 +1381,19 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { myObservationDao.create(o, new ServletRequestDetails()); - 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()); @@ -1406,8 +1403,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); @@ -1555,7 +1552,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)); @@ -1597,7 +1594,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()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java index 76ff2f8b6ab..f79375cd079 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java @@ -42,8 +42,10 @@ import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.After; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; @@ -112,7 +114,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { // Try making the resource unparseable TransactionTemplate template = new TransactionTemplate(myTxManager); - template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); template.execute(new TransactionCallback() { @Override public ResourceTable doInTransaction(TransactionStatus theStatus) { @@ -305,6 +307,100 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } + @After + public void after() { + myDaoConfig.setAllowInlineMatchUrlReferences(false); + } + + @Test + public void testTransactionCreateInlineMatchUrlWithOneMatch() { + String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch"; + Bundle request = new Bundle(); + + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType id = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(myRequestDetails, request); + assertEquals(1, resp.getEntry().size()); + + BundleEntryComponent respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); + assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); + assertEquals("1", o.getIdElement().getVersionIdPart()); + + } + + @Test + public void testTransactionCreateInlineMatchUrlWithNoMatches() { + String methodName = "testTransactionCreateInlineMatchUrlWithNoMatches"; + Bundle request = new Bundle(); + + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + myPatientDao.create(p, mySrd).getId(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + myPatientDao.create(p, mySrd).getId(); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(myRequestDetails, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithNoMatches\" - Multiple resources match this search", e.getMessage()); + } + } + + @Test + public void testTransactionCreateInlineMatchUrlWithTwoMatches() { + String methodName = "testTransactionCreateInlineMatchUrlWithTwoMatches"; + Bundle request = new Bundle(); + + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + myPatientDao.create(p, mySrd).getId(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + myPatientDao.create(p, mySrd).getId(); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + try { + mySystemDao.transaction(myRequestDetails, request); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithTwoMatches\" - Multiple resources match this search", e.getMessage()); + } + } + @Test public void testTransactionCreateMatchUrlWithTwoMatch() { String methodName = "testTransactionCreateMatchUrlWithTwoMatch"; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index c8b718df305..0cdc9fe7462 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -50,6 +50,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { retVal.setSubscriptionPollDelay(5000); retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR); retVal.setAllowMultipleDelete(true); + retVal.setAllowInlineMatchUrlReferences(true); return retVal; } diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java index 42f20d2d5dc..e1b92db9eda 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java @@ -46,6 +46,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { retVal.setSubscriptionPollDelay(5000); retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR); retVal.setAllowMultipleDelete(true); + retVal.setAllowInlineMatchUrlReferences(true); return retVal; } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java index aee3a682be8..88ebaf5c00f 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java @@ -1,7 +1,9 @@ package ca.uhn.fhir.rest.client; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; @@ -17,11 +19,9 @@ import org.apache.http.message.BasicStatusLine; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.dstu.resource.Binary; import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.resource.Patient; @@ -48,7 +48,7 @@ public class BinaryClientTest { httpClient = mock(HttpClient.class, new ReturnsDeepStubs()); ctx.getRestfulClientFactory().setHttpClient(httpClient); - ctx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.NEVER); + ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); } @@ -59,7 +59,7 @@ public class BinaryClientTest { when(httpClient.execute(capt.capture())).thenReturn(httpResponse); when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", "foo/bar")); - when(httpResponse.getEntity().getContent()).thenReturn(new ByteArrayInputStream(new byte[] {1,2,3,4})); + when(httpResponse.getEntity().getContent()).thenReturn(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 })); IClient client = ctx.newRestfulClient(IClient.class, "http://foo"); Binary resp = client.read(new IdDt("http://foo/Patient/123")); @@ -67,24 +67,17 @@ public class BinaryClientTest { assertEquals(HttpGet.class, capt.getValue().getClass()); HttpGet get = (HttpGet) capt.getValue(); assertEquals("http://foo/Binary/123", get.getURI().toString()); - + assertEquals("foo/bar", resp.getContentType()); assertArrayEquals(new byte[] { 1, 2, 3, 4 }, resp.getContent()); } - public static void main(String[] args) { - - IClient c = Mockito.mock(IClient.class, new ReturnsDeepStubs()); - - } - - @Test public void testCreate() throws Exception { Binary res = new Binary(); res.setContent(new byte[] { 1, 2, 3, 4 }); res.setContentType("text/plain"); - + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(httpClient.execute(capt.capture())).thenReturn(httpResponse); when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); @@ -92,29 +85,23 @@ public class BinaryClientTest { when(httpResponse.getEntity().getContent()).thenReturn(new ByteArrayInputStream(new byte[] {})); IClient client = ctx.newRestfulClient(IClient.class, "http://foo"); - MethodOutcome resp = client.create(res); + client.create(res); assertEquals(HttpPost.class, capt.getValue().getClass()); HttpPost post = (HttpPost) capt.getValue(); assertEquals("http://foo/Binary", post.getURI().toString()); - - assertEquals("text/plain", post.getEntity().getContentType().getValue()); + + assertEquals("text/plain", capt.getValue().getFirstHeader("Content-Type").getValue()); assertArrayEquals(new byte[] { 1, 2, 3, 4 }, IOUtils.toByteArray(post.getEntity().getContent())); } - - private String createBundle() { - return ctx.newXmlParser().encodeBundleToString(new Bundle()); - } - - private interface IClient extends IBasicClient { - @Read(type=Binary.class) + @Read(type = Binary.class) public Binary read(@IdParam IdDt theBinary); - @Create(type=Binary.class) + @Create(type = Binary.class) public MethodOutcome create(@ResourceParam Binary theBinary); } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DynamicSearchTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DynamicSearchTest.java index 63d06a264b1..a72e55159d0 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DynamicSearchTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DynamicSearchTest.java @@ -173,8 +173,8 @@ public class DynamicSearchTest { @Override public List getSearchParameters() { ArrayList retVal = new ArrayList(); - retVal.add(new RuntimeSearchParam("param1", "This is the first parameter", "Patient.param1", RestSearchParameterTypeEnum.STRING)); - retVal.add(new RuntimeSearchParam("param2", "This is the second parameter", "Patient.param2", RestSearchParameterTypeEnum.DATE)); + retVal.add(new RuntimeSearchParam("param1", "This is the first parameter", "Patient.param1", RestSearchParameterTypeEnum.STRING, null)); + retVal.add(new RuntimeSearchParam("param2", "This is the second parameter", "Patient.param2", RestSearchParameterTypeEnum.DATE, null)); return retVal; } diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java index bdaa8711e0b..fb583c22261 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java @@ -143,7 +143,7 @@ public class GenericClientDstu2Test { client.fetchConformance().ofType(Conformance.class).execute(); assertEquals("http://example.com/fhir/metadata", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(1, capt.getAllValues().get(idx).getHeaders("Accept").length); - assertThat(capt.getAllValues().get(idx).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL)); + assertThat(capt.getAllValues().get(idx).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON)); idx++; client.fetchConformance().ofType(Conformance.class).encodedJson().execute(); @@ -192,12 +192,12 @@ public class GenericClientDstu2Test { assertEquals("FAMILY", resp.getName().get(0).getFamily().get(0).getValue()); assertEquals("http://" + methodName + ".example.com/fhir/metadata", capt.getAllValues().get(0).getURI().toASCIIString()); assertEquals(1, capt.getAllValues().get(0).getHeaders("Accept").length); - assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL)); + assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON)); assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_XML)); assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_JSON)); assertEquals("http://" + methodName + ".example.com/fhir/Patient/123", capt.getAllValues().get(1).getURI().toASCIIString()); assertEquals(1, capt.getAllValues().get(1).getHeaders("Accept").length); - assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL)); + assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON)); assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_XML)); assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_JSON)); } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java new file mode 100644 index 00000000000..c7066560f2a --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java @@ -0,0 +1,219 @@ +package ca.uhn.fhir.rest.client; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.Arrays; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.hl7.fhir.dstu3.model.Binary; +import org.hl7.fhir.dstu3.model.Conformance; +import org.hl7.fhir.dstu3.model.OperationOutcome; +import org.hl7.fhir.dstu3.model.Patient; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.util.VersionUtil; + +public class GenericClientDstu3Test { + private static FhirContext ourCtx; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientDstu3Test.class); + private HttpClient myHttpClient; + private HttpResponse myHttpResponse; + + @Before + public void before() { + myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + } + + private byte[] extractBodyAsByteArray(ArgumentCaptor capt) throws IOException { + byte[] body = IOUtils.toByteArray(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent()); + return body; + } + + private String extractBodyAsString(ArgumentCaptor capt) throws IOException { + String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent(), "UTF-8"); + return body; + } + + @Test + public void testUserAgentForConformance() throws Exception { + IParser p = ourCtx.newXmlParser(); + + Conformance conf = new Conformance(); + conf.setCopyright("COPY"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + client.fetchConformance().ofType(Conformance.class).execute(); + assertEquals("http://example.com/fhir/metadata", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + } + + @Test + public void testUserAgentForBinary() throws Exception { + IParser p = ourCtx.newXmlParser(); + + Conformance conf = new Conformance(); + conf.setCopyright("COPY"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Binary bin = new Binary(); + bin.setContentType("application/foo"); + bin.setContent(new byte[] { 0, 1, 2, 3, 4 }); + client.create().resource(bin).execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/foo", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, extractBodyAsByteArray(capt)); + + } + + @Test + public void testBinaryCreateWithNoContentType() throws Exception { + IParser p = ourCtx.newXmlParser(); + + OperationOutcome conf = new OperationOutcome(); + conf.getText().setDivAsString("OK!"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Binary bin = new Binary(); + bin.setContent(new byte[] { 0, 1, 2, 3, 4 }); + client.create().resource(bin).execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); + + } + + @Test + public void testBinaryCreateWithFhirContentType() throws Exception { + IParser p = ourCtx.newXmlParser(); + + OperationOutcome conf = new OperationOutcome(); + conf.getText().setDivAsString("OK!"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.getText().setDivAsString("A PATIENT"); + + Binary bin = new Binary(); + bin.setContent(ourCtx.newJsonParser().encodeResourceToString(pt).getBytes("UTF-8")); + bin.setContentType(Constants.CT_FHIR_JSON); + client.create().resource(bin).execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + Binary output = ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)); + assertEquals(Constants.CT_FHIR_JSON, output.getContentType()); + + Patient outputPt = (Patient) ourCtx.newJsonParser().parseResource(new String(output.getContent(), "UTF-8")); + assertEquals("
A PATIENT
", outputPt.getText().getDivAsString()); + } + + private void validateUserAgent(ArgumentCaptor capt) { + assertEquals(1, capt.getAllValues().get(0).getHeaders("User-Agent").length); + assertEquals(expectedUserAgent(), capt.getAllValues().get(0).getHeaders("User-Agent")[0].getValue()); + } + + private String expectedUserAgent() { + return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client)"; + } + + @BeforeClass + public static void beforeClass() { + ourCtx = FhirContext.forDstu3(); + } + +} diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/CreateBinaryDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/CreateBinaryDstu3Test.java new file mode 100644 index 00000000000..88a39f956a7 --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/CreateBinaryDstu3Test.java @@ -0,0 +1,161 @@ +package ca.uhn.fhir.rest.server; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.concurrent.TimeUnit; + +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.dstu3.model.Binary; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.util.PortUtil; + +public class CreateBinaryDstu3Test { + private static CloseableHttpClient ourClient; + private static int ourPort; + private static FhirContext ourCtx = FhirContext.forDstu3(); + private static Server ourServer; + private static Binary ourLastBinary; + private static String ourLastBinaryString; + private static byte[] ourLastBinaryBytes; + + + + @Before + public void before() { + ourLastBinary = null; + ourLastBinaryBytes = null; + ourLastBinaryString = null; + } + + + @Test + public void testRawBytesNoContentType() throws Exception { + HttpPost post = new HttpPost("http://localhost:" + ourPort + "/Binary"); + post.setEntity(new ByteArrayEntity(new byte[] {0,1,2,3,4})); + ourClient.execute(post); + + assertNull(ourLastBinary.getContentType()); + assertArrayEquals(new byte[] {0,1,2,3,4}, ourLastBinary.getContent()); + } + + @Test + public void testRawBytesBinaryContentType() throws Exception { + HttpPost post = new HttpPost("http://localhost:" + ourPort + "/Binary"); + post.setEntity(new ByteArrayEntity(new byte[] {0,1,2,3,4})); + post.addHeader("Content-Type", "application/foo"); + ourClient.execute(post); + + assertEquals("application/foo", ourLastBinary.getContentType()); + assertArrayEquals(new byte[] {0,1,2,3,4}, ourLastBinary.getContent()); + assertArrayEquals(new byte[] {0,1,2,3,4}, ourLastBinaryBytes); + } + + /** + * Technically the client shouldn't be doing it this way, + * but we'll be accepting + */ + @Test + public void testRawBytesFhirContentType() throws Exception { + + Binary b = new Binary(); + b.setContentType("application/foo"); + b.setContent(new byte[] {0,1,2,3,4}); + String encoded = ourCtx.newJsonParser().encodeResourceToString(b); + + HttpPost post = new HttpPost("http://localhost:" + ourPort + "/Binary"); + post.setEntity(new StringEntity(encoded)); + post.addHeader("Content-Type", Constants.CT_FHIR_JSON); + ourClient.execute(post); + + assertEquals("application/foo", ourLastBinary.getContentType()); + assertArrayEquals(new byte[] {0,1,2,3,4}, ourLastBinary.getContent()); + } + + @Test + public void testRawBytesFhirContentTypeContainingFhir() throws Exception { + + Patient p = new Patient(); + p.getText().setDivAsString("A PATIENT"); + + Binary b = new Binary(); + b.setContentType("application/xml+fhir"); + b.setContent(ourCtx.newXmlParser().encodeResourceToString(p).getBytes("UTF-8")); + String encoded = ourCtx.newJsonParser().encodeResourceToString(b); + + HttpPost post = new HttpPost("http://localhost:" + ourPort + "/Binary"); + post.setEntity(new StringEntity(encoded)); + post.addHeader("Content-Type", Constants.CT_FHIR_JSON); + ourClient.execute(post); + + assertEquals("application/xml+fhir", ourLastBinary.getContentType()); + assertArrayEquals(b.getContent(), ourLastBinary.getContent()); + assertEquals(encoded, ourLastBinaryString); + assertArrayEquals(encoded.getBytes("UTF-8"), ourLastBinaryBytes); + } + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + + @BeforeClass + public static void beforeClass() throws Exception { + ourPort = PortUtil.findFreePort(); + ourServer = new Server(ourPort); + + BinaryProvider binaryProvider = new BinaryProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer servlet = new RestfulServer(ourCtx); + servlet.setResourceProviders(binaryProvider); + ServletHolder servletHolder = new ServletHolder(servlet); + proxyHandler.addServletWithMapping(servletHolder, "/*"); + ourServer.setHandler(proxyHandler); + ourServer.start(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourClient = builder.build(); + } + + public static class BinaryProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Binary.class; + } + + @Create() + public MethodOutcome createBinary(@ResourceParam Binary theBinary, @ResourceParam String theBinaryString, @ResourceParam byte[] theBinaryBytes) { + ourLastBinary = theBinary; + ourLastBinaryString = theBinaryString; + ourLastBinaryBytes = theBinaryBytes; + return new MethodOutcome(new IdType("Binary/001/_history/002")); + } + + } + +} 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}"; /** diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 6509a2d9d09..b3fea478314 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -59,6 +59,50 @@ JPA server now supports :above and :below qualifiers on URI search params + + Add optional support (disabled by default for now) to JPA server to support + inline references containing search URLs. These URLs will be resolved when + a resource is being created/updated and replaced with the single matching + resource. This is being used as a part of the May 2016 Connectathon for + a testing scenario. + + + The server no longer adds a + WWW-Authenticate]]> + header to the response if any resource provider code throws an + AuthenticationException]]>. This header is + used for interactive authentication, which isn't generally + appropriate for FHIR. We added code to add this header a long time + ago for testing purposes and it never got removed. Please let us + know if you need the ability to add this header automatically. Thanks + to Lars Kristian Roland for pointing this out. + + + In the client, the create/update operations on a Binary resource + (which use the raw binary's content type as opposed to the FHIR + content type) were not including any request headers (Content-Type, + User-Agent, etc.) Thanks to Peter Van Houte of Agfa Healthcare for + reporting! + + + Handling of Binary resources containing embedded FHIR resources for + create/update/etc operations has been corrected per the FHIR rules + outlined at + Binary Resource in both + the client and server. +
]]> + Essentially, if the Binary contains something + that isn't FHIR (e.g. an image with an image content-type) the + client will send the raw data with the image content type to the server. The + server will place the content type and raw data into a Binary resource instance + and pass those to the resource provider. This part was already correct previous + to 1.5. +
]]> + On the other hand, if the Binary contains a FHIR content type, the Binary + is now sent by the client to the server as a Binary resource with a FHIR content-type, + and the embedded FHIR content is contained in the appropriate fields. The server + will pass this "outer" Binary resource to the resource provider code. +
The RequestDetails and ActionRequestDetails objects which are passed to server interceptor methods and may also be used as server provider method diff --git a/src/site/xdoc/doc_rest_operations.xml b/src/site/xdoc/doc_rest_operations.xml index 80b02082698..97f7b0c424d 100644 --- a/src/site/xdoc/doc_rest_operations.xml +++ b/src/site/xdoc/doc_rest_operations.xml @@ -115,8 +115,9 @@ Update methods must be annotated with the @Update annotation, and have a parameter annotated with the - @Resource + @ResourceParam annotation. This parameter contains the resource instance to be created. + See the @ResourceParam for information on the types allowed for this parameter (resource types, String, byte[]).

In addition, the method may optionally have a parameter annotated with the @@ -316,8 +317,9 @@ Create methods must be annotated with the @Create annotation, and have a single parameter annotated with the - @Resource + @ResourceParam annotation. This parameter contains the resource instance to be created. + See the @ResourceParam for information on the types allowed for this parameter (resource types, String, byte[]).

Create methods must return an object of type @@ -1104,7 +1106,7 @@ Validate methods must be annotated with the @Validate annotation, and have a parameter annotated with the - @Resource + @ResourceParam annotation. This parameter contains the resource instance to be created.