Merge remote-tracking branch 'upstream/master' and fix tinder-test

conflict
This commit is contained in:
bdenton 2018-11-29 09:45:00 -08:00
commit 689340368e
235 changed files with 9349 additions and 4105 deletions

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -10,7 +10,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -10,7 +10,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -10,7 +10,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<artifactId>hapi-fhir-standalone-overlay-example</artifactId> <artifactId>hapi-fhir-standalone-overlay-example</artifactId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -73,12 +73,12 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeElementCompositeDefinition.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeElementCompositeDefinition.class);
private Map<String, Integer> forcedOrder = null; private Map<String, Integer> forcedOrder = null;
private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<BaseRuntimeChildDefinition>(); private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<>();
private List<BaseRuntimeChildDefinition> myChildrenAndExtensions; private List<BaseRuntimeChildDefinition> myChildrenAndExtensions;
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions; private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions;
private FhirContext myContext; private final FhirContext myContext;
private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>(); private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<>();
private List<ScannedField> myScannedFields = new ArrayList<BaseRuntimeElementCompositeDefinition.ScannedField>(); private List<ScannedField> myScannedFields = new ArrayList<>();
private volatile boolean mySealed; private volatile boolean mySealed;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -92,12 +92,12 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
* We scan classes for annotated fields in the class but also all of its superclasses * We scan classes for annotated fields in the class but also all of its superclasses
*/ */
Class<? extends IBase> current = theImplementingClass; Class<? extends IBase> current = theImplementingClass;
LinkedList<Class<? extends IBase>> classes = new LinkedList<Class<? extends IBase>>(); LinkedList<Class<? extends IBase>> classes = new LinkedList<>();
do { do {
if (forcedOrder == null) { if (forcedOrder == null) {
ChildOrder childOrder = current.getAnnotation(ChildOrder.class); ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
if (childOrder != null) { if (childOrder != null) {
forcedOrder = new HashMap<String, Integer>(); forcedOrder = new HashMap<>();
for (int i = 0; i < childOrder.names().length; i++) { for (int i = 0; i < childOrder.names().length; i++) {
String nextName = childOrder.names()[i]; String nextName = childOrder.names()[i];
if (nextName.endsWith("[x]")) { if (nextName.endsWith("[x]")) {
@ -115,7 +115,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
} }
} while (current != null); } while (current != null);
Set<Field> fields = new HashSet<Field>(); Set<Field> fields = new HashSet<>();
for (Class<? extends IBase> nextClass : classes) { for (Class<? extends IBase> nextClass : classes) {
int fieldIndexInClass = 0; int fieldIndexInClass = 0;
for (Field next : nextClass.getDeclaredFields()) { for (Field next : nextClass.getDeclaredFields()) {
@ -192,9 +192,9 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
} }
private void scanCompositeElementForChildren() { private void scanCompositeElementForChildren() {
Set<String> elementNames = new HashSet<String>(); Set<String> elementNames = new HashSet<>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>(); TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>(); TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<>();
scanCompositeElementForChildren(elementNames, orderToElementDef, orderToExtensionDef); scanCompositeElementForChildren(elementNames, orderToElementDef, orderToExtensionDef);
@ -203,7 +203,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
* Find out how many elements don't match any entry in the list * Find out how many elements don't match any entry in the list
* for forced order. Those elements come first. * for forced order. Those elements come first.
*/ */
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>(); TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<>();
int unknownCount = 0; int unknownCount = 0;
for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) { for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
if (!forcedOrder.containsKey(nextEntry.getElementName())) { if (!forcedOrder.containsKey(nextEntry.getElementName())) {
@ -220,7 +220,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
orderToElementDef = newOrderToExtensionDef; orderToElementDef = newOrderToExtensionDef;
} }
TreeSet<Integer> orders = new TreeSet<Integer>(); TreeSet<Integer> orders = new TreeSet<>();
orders.addAll(orderToElementDef.keySet()); orders.addAll(orderToElementDef.keySet());
orders.addAll(orderToExtensionDef.keySet()); orders.addAll(orderToExtensionDef.keySet());
@ -329,7 +329,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
* 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) { if (order == Child.ORDER_UNKNOWN) {
order = Integer.valueOf(0); order = 0;
while (orderMap.containsKey(order)) { while (orderMap.containsKey(order)) {
order++; order++;
} }
@ -386,7 +386,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
/* /*
* Child is a resource reference * Child is a resource reference
*/ */
List<Class<? extends IBaseResource>> refTypesList = new ArrayList<Class<? extends IBaseResource>>(); List<Class<? extends IBaseResource>> refTypesList = new ArrayList<>();
for (Class<? extends IElement> nextType : childAnnotation.type()) { for (Class<? extends IElement> nextType : childAnnotation.type()) {
if (IBaseReference.class.isAssignableFrom(nextType)) { if (IBaseReference.class.isAssignableFrom(nextType)) {
refTypesList.add(myContext.getVersion().getVersion().isRi() ? IAnyResource.class : IResource.class); refTypesList.add(myContext.getVersion().getVersion().isRi() ? IAnyResource.class : IResource.class);
@ -469,10 +469,10 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
next.sealAndInitialize(theContext, theClassToElementDefinitions); next.sealAndInitialize(theContext, theClassToElementDefinitions);
} }
myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>(); myNameToChild = new HashMap<>();
for (BaseRuntimeChildDefinition next : myChildren) { for (BaseRuntimeChildDefinition next : myChildren) {
if (next instanceof RuntimeChildChoiceDefinition) { if (next instanceof RuntimeChildChoiceDefinition) {
String key = ((RuntimeChildChoiceDefinition) next).getElementName()+"[x]"; String key = next.getElementName()+"[x]";
myNameToChild.put(key, next); myNameToChild.put(key, next);
} }
for (String nextName : next.getValidChildNames()) { for (String nextName : next.getValidChildNames()) {
@ -486,7 +486,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
myChildren = Collections.unmodifiableList(myChildren); myChildren = Collections.unmodifiableList(myChildren);
myNameToChild = Collections.unmodifiableMap(myNameToChild); myNameToChild = Collections.unmodifiableMap(myNameToChild);
List<BaseRuntimeChildDefinition> children = new ArrayList<BaseRuntimeChildDefinition>(); List<BaseRuntimeChildDefinition> children = new ArrayList<>();
children.addAll(myChildren); children.addAll(myChildren);
/* /*
@ -554,11 +554,11 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
private static class ScannedField { private static class ScannedField {
private Child myChildAnnotation; private Child myChildAnnotation;
private List<Class<? extends IBase>> myChoiceTypes = new ArrayList<Class<? extends IBase>>(); private List<Class<? extends IBase>> myChoiceTypes = new ArrayList<>();
private Class<?> myElementType; private Class<?> myElementType;
private Field myField; private Field myField;
private boolean myFirstFieldInNewClass; private boolean myFirstFieldInNewClass;
public ScannedField(Field theField, Class<?> theClass, boolean theFirstFieldInNewClass) { ScannedField(Field theField, Class<?> theClass, boolean theFirstFieldInNewClass) {
myField = theField; myField = theField;
myFirstFieldInNewClass = theFirstFieldInNewClass; myFirstFieldInNewClass = theFirstFieldInNewClass;
@ -575,9 +575,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
myChildAnnotation = childAnnotation; myChildAnnotation = childAnnotation;
myElementType = ModelScanner.determineElementType(theField); myElementType = ModelScanner.determineElementType(theField);
for (Class<? extends IBase> nextChoiceType : childAnnotation.type()) { Collections.addAll(myChoiceTypes, childAnnotation.type());
myChoiceTypes.add(nextChoiceType);
}
} }
public Child getChildAnnotation() { public Child getChildAnnotation() {

View File

@ -19,17 +19,6 @@ package ca.uhn.fhir.context;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum; import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum;
import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.api.*;
@ -38,6 +27,19 @@ import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.ReflectionUtil;
import org.hl7.fhir.instance.model.api.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.Map.Entry;
import static org.apache.commons.lang3.StringUtils.isBlank;
class ModelScanner { class ModelScanner {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
@ -55,7 +57,7 @@ class ModelScanner {
private Set<Class<? extends IBase>> myVersionTypes; private Set<Class<? extends IBase>> myVersionTypes;
ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions, ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions,
Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException { Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
myContext = theContext; myContext = theContext;
myVersion = theVersion; myVersion = theVersion;
Set<Class<? extends IBase>> toScan; Set<Class<? extends IBase>> toScan;
@ -67,32 +69,6 @@ class ModelScanner {
init(theExistingDefinitions, toScan); init(theExistingDefinitions, toScan);
} }
static Class<?> determineElementType(Field next) {
Class<?> nextElementType = next.getType();
if (List.class.equals(nextElementType)) {
nextElementType = ReflectionUtil.getGenericCollectionTypeOfField(next);
} else if (Collection.class.isAssignableFrom(nextElementType)) {
throw new ConfigurationException("Field '" + next.getName() + "' in type '" + next.getClass().getCanonicalName() + "' is a Collection - Only java.util.List curently supported");
}
return nextElementType;
}
@SuppressWarnings("unchecked")
static IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
Class<?> bound = getGenericCollectionTypeOfCodedField(theNext);
if (bound == null) {
throw new ConfigurationException("Field '" + theNext + "' has no parameter for " + BoundCodeDt.class.getSimpleName() + " to determine enum type");
}
String fieldName = "VALUESET_BINDER";
try {
Field bindingField = bound.getField(fieldName);
return (IValueSetEnumBinder<Enum<?>>) bindingField.get(null);
} catch (Exception e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field (must have a field called " + fieldName + ")", e);
}
}
public Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() { public Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
return myClassToElementDefinitions; return myClassToElementDefinitions;
} }
@ -137,11 +113,7 @@ class ModelScanner {
for (Class<? extends IBase> nextClass : typesToScan) { for (Class<? extends IBase> nextClass : typesToScan) {
scan(nextClass); scan(nextClass);
} }
for (Iterator<Class<? extends IBase>> iter = myScanAlso.iterator(); iter.hasNext();) { myScanAlso.removeIf(theClass -> myClassToElementDefinitions.containsKey(theClass));
if (myClassToElementDefinitions.containsKey(iter.next())) {
iter.remove();
}
}
typesToScan.clear(); typesToScan.clear();
typesToScan.addAll(myScanAlso); typesToScan.addAll(myScanAlso);
myScanAlso.clear(); myScanAlso.clear();
@ -177,16 +149,6 @@ class ModelScanner {
return retVal; return retVal;
} }
/**
* There are two implementations of all of the annotations (e.g. {@link 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.
*/
static <T extends Annotation> T pullAnnotation(AnnotatedElement theTarget, Class<T> theAnnotationType) {
T retVal = theTarget.getAnnotation(theAnnotationType);
return retVal;
}
private void scan(Class<? extends IBase> theClass) throws ConfigurationException { private void scan(Class<? extends IBase> theClass) throws ConfigurationException {
BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass); BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
if (existingDef != null) { if (existingDef != null) {
@ -197,7 +159,7 @@ class ModelScanner {
if (resourceDefinition != null) { if (resourceDefinition != null) {
if (!IBaseResource.class.isAssignableFrom(theClass)) { if (!IBaseResource.class.isAssignableFrom(theClass)) {
throw new ConfigurationException( throw new ConfigurationException(
"Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName()); "Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName());
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<? extends IBaseResource> resClass = (Class<? extends IBaseResource>) theClass; Class<? extends IBaseResource> resClass = (Class<? extends IBaseResource>) theClass;
@ -212,7 +174,7 @@ class ModelScanner {
Class<? extends ICompositeType> resClass = (Class<? extends ICompositeType>) theClass; Class<? extends ICompositeType> resClass = (Class<? extends ICompositeType>) theClass;
scanCompositeDatatype(resClass, datatypeDefinition); scanCompositeDatatype(resClass, datatypeDefinition);
} else if (IPrimitiveType.class.isAssignableFrom(theClass)) { } else if (IPrimitiveType.class.isAssignableFrom(theClass)) {
@SuppressWarnings({ "unchecked" }) @SuppressWarnings({"unchecked"})
Class<? extends IPrimitiveType<?>> resClass = (Class<? extends IPrimitiveType<?>>) theClass; Class<? extends IPrimitiveType<?>> resClass = (Class<? extends IPrimitiveType<?>>) theClass;
scanPrimitiveDatatype(resClass, datatypeDefinition); scanPrimitiveDatatype(resClass, datatypeDefinition);
} }
@ -227,13 +189,13 @@ class ModelScanner {
scanBlock(theClass); scanBlock(theClass);
} else { } else {
throw new ConfigurationException( throw new ConfigurationException(
"Type contains a @" + Block.class.getSimpleName() + " annotation but does not implement " + IResourceBlock.class.getCanonicalName() + ": " + theClass.getCanonicalName()); "Type contains a @" + Block.class.getSimpleName() + " annotation but does not implement " + IResourceBlock.class.getCanonicalName() + ": " + theClass.getCanonicalName());
} }
} }
if (blockDefinition == null if (blockDefinition == null
//Redundant checking && datatypeDefinition == null && resourceDefinition == null //Redundant checking && datatypeDefinition == null && resourceDefinition == null
) { ) {
throw new ConfigurationException("Resource class[" + theClass.getName() + "] does not contain any valid HAPI-FHIR annotations"); throw new ConfigurationException("Resource class[" + theClass.getName() + "] does not contain any valid HAPI-FHIR annotations");
} }
} }
@ -246,7 +208,16 @@ class ModelScanner {
throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName()); throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
} }
// Just in case someone messes up when upgrading from DSTU2
if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
if (BaseIdentifiableElement.class.isAssignableFrom(theClass)) {
throw new ConfigurationException("@Block class for version " + myContext.getVersion().getVersion().name() + " should not extend " + BaseIdentifiableElement.class.getSimpleName() + ": " + theClass.getName());
}
}
RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions); RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions);
blockDef.populateScanAlso(myScanAlso);
myClassToElementDefinitions.put(theClass, blockDef); myClassToElementDefinitions.put(theClass, blockDef);
} }
@ -272,14 +243,6 @@ class ModelScanner {
elementDef.populateScanAlso(myScanAlso); elementDef.populateScanAlso(myScanAlso);
} }
static Class<? extends Enum<?>> determineEnumTypeForBoundField(Field next) {
@SuppressWarnings("unchecked")
Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next);
return enumType;
}
private String scanPrimitiveDatatype(Class<? extends IPrimitiveType<?>> theClass, DatatypeDef theDatatypeDefinition) { private String scanPrimitiveDatatype(Class<? extends IPrimitiveType<?>> theClass, DatatypeDef theDatatypeDefinition) {
ourLog.debug("Scanning resource class: {}", theClass.getName()); ourLog.debug("Scanning resource class: {}", theClass.getName());
@ -333,7 +296,7 @@ class ModelScanner {
} }
if (isBlank(resourceName)) { if (isBlank(resourceName)) {
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name(): " + theClass.getCanonicalName() 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 "); + " - This is only allowed for types that extend other resource types ");
} }
} }
@ -350,7 +313,7 @@ class ModelScanner {
if (!isBlank(resourceId)) { if (!isBlank(resourceId)) {
if (myIdToResourceDefinition.containsKey(resourceId)) { if (myIdToResourceDefinition.containsKey(resourceId)) {
throw new ConfigurationException("The following resource types have the same ID of '" + resourceId + "' - " + theClass.getCanonicalName() + " and " throw new ConfigurationException("The following resource types have the same ID of '" + resourceId + "' - " + theClass.getCanonicalName() + " and "
+ myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName()); + myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName());
} }
} }
@ -442,7 +405,7 @@ class ModelScanner {
RuntimeSearchParam param = nameToParam.get(nextName); RuntimeSearchParam param = nameToParam.get(nextName);
if (param == null) { 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: {}", 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() }); new Object[]{theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()});
continue; continue;
} }
compositeOf.add(param); compositeOf.add(param);
@ -466,6 +429,48 @@ class ModelScanner {
return retVal; return retVal;
} }
static Class<?> determineElementType(Field next) {
Class<?> nextElementType = next.getType();
if (List.class.equals(nextElementType)) {
nextElementType = ReflectionUtil.getGenericCollectionTypeOfField(next);
} else if (Collection.class.isAssignableFrom(nextElementType)) {
throw new ConfigurationException("Field '" + next.getName() + "' in type '" + next.getClass().getCanonicalName() + "' is a Collection - Only java.util.List curently supported");
}
return nextElementType;
}
@SuppressWarnings("unchecked")
static IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
Class<?> bound = getGenericCollectionTypeOfCodedField(theNext);
if (bound == null) {
throw new ConfigurationException("Field '" + theNext + "' has no parameter for " + BoundCodeDt.class.getSimpleName() + " to determine enum type");
}
String fieldName = "VALUESET_BINDER";
try {
Field bindingField = bound.getField(fieldName);
return (IValueSetEnumBinder<Enum<?>>) bindingField.get(null);
} catch (Exception e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field (must have a field called " + fieldName + ")", e);
}
}
/**
* There are two implementations of all of the annotations (e.g. {@link 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.
*/
static <T extends Annotation> T pullAnnotation(AnnotatedElement theTarget, Class<T> theAnnotationType) {
T retVal = theTarget.getAnnotation(theAnnotationType);
return retVal;
}
static Class<? extends Enum<?>> determineEnumTypeForBoundField(Field next) {
@SuppressWarnings("unchecked")
Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next);
return enumType;
}
private static Class<?> getGenericCollectionTypeOfCodedField(Field next) { private static Class<?> getGenericCollectionTypeOfCodedField(Field next) {
Class<?> type; Class<?> type;
ParameterizedType collectionType = (ParameterizedType) next.getGenericType(); ParameterizedType collectionType = (ParameterizedType) next.getGenericType();

View File

@ -149,9 +149,9 @@ public class RuntimeChildUndeclaredExtensionDefinition extends BaseRuntimeChildD
@Override @Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
Map<String, BaseRuntimeElementDefinition<?>> datatypeAttributeNameToDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>(); Map<String, BaseRuntimeElementDefinition<?>> datatypeAttributeNameToDefinition = new HashMap<>();
myDatatypeToAttributeName = new HashMap<Class<? extends IBase>, String>(); myDatatypeToAttributeName = new HashMap<>();
myDatatypeToDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>(); myDatatypeToDefinition = new HashMap<>();
for (BaseRuntimeElementDefinition<?> next : theClassToElementDefinitions.values()) { for (BaseRuntimeElementDefinition<?> next : theClassToElementDefinitions.values()) {
if (next instanceof IRuntimeDatatypeDefinition) { if (next instanceof IRuntimeDatatypeDefinition) {

View File

@ -60,7 +60,7 @@ public interface IQueryParameterType extends Serializable {
public String getValueAsQueryToken(FhirContext theContext); public String getValueAsQueryToken(FhirContext theContext);
/** /**
* This method will return any qualifier that should be appended to the parameter name (e.g ":exact") * This method will return any qualifier that should be appended to the parameter name (e.g ":exact"). Returns null if none are present.
*/ */
public String getQueryParameterQualifier(); public String getQueryParameterQualifier();

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
@ -623,6 +624,16 @@ public abstract class BaseParser implements IParser {
return mySuppressNarratives; return mySuppressNarratives;
} }
@Override
public IBaseResource parseResource(InputStream theInputStream) throws DataFormatException {
return parseResource(new InputStreamReader(theInputStream, Charsets.UTF_8));
}
@Override
public <T extends IBaseResource> T parseResource(Class<T> theResourceType, InputStream theInputStream) throws DataFormatException {
return parseResource(theResourceType, new InputStreamReader(theInputStream, Charsets.UTF_8));
}
@Override @Override
public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException { public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {

View File

@ -19,14 +19,23 @@ package ca.uhn.fhir.parser;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import java.io.*;
import java.util.*;
import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.*; import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
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 java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/** /**
* A parser, which can be used to convert between HAPI FHIR model/structure objects, and their respective String wire * A parser, which can be used to convert between HAPI FHIR model/structure objects, and their respective String wire
@ -127,6 +136,20 @@ public interface IParser {
*/ */
<T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException; <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
/**
* Parses a resource
*
* @param theResourceType
* The resource type to use. This can be used to explicitly specify a class which extends a built-in type
* (e.g. a custom type extending the default Patient class)
* @param theInputStream
* The InputStream to parse input from, <b>with an implied charset of UTF-8</b>. Note that the InputStream will not be closed by the parser upon completion.
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
<T extends IBaseResource> T parseResource(Class<T> theResourceType, InputStream theInputStream) throws DataFormatException;
/** /**
* Parses a resource * Parses a resource
* *
@ -153,6 +176,19 @@ public interface IParser {
*/ */
IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException; IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException;
/**
* Parses a resource
*
* @param theInputStream
* The InputStream to parse input from (charset is assumed to be UTF-8).
* Note that the stream will not be closed by the parser upon completion.
* @return A parsed resource. Note that the returned object will be an instance of {@link IResource} or
* {@link IAnyResource} depending on the specific FhirContext which created this parser.
* @throws DataFormatException
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
IBaseResource parseResource(InputStream theInputStream) throws ConfigurationException, DataFormatException;
/** /**
* Parses a resource * Parses a resource
* *

View File

@ -42,8 +42,14 @@ public class Constants {
*/ */
public static final Set<String> CORS_ALLWED_METHODS; public static final Set<String> CORS_ALLWED_METHODS;
public static final String CT_FHIR_JSON = "application/json+fhir"; public static final String CT_FHIR_JSON = "application/json+fhir";
/**
* The FHIR MimeType for JSON encoding in FHIR DSTU3+
*/
public static final String CT_FHIR_JSON_NEW = "application/fhir+json"; public static final String CT_FHIR_JSON_NEW = "application/fhir+json";
public static final String CT_FHIR_XML = "application/xml+fhir"; public static final String CT_FHIR_XML = "application/xml+fhir";
/**
* The FHIR MimeType for XML encoding in FHIR DSTU3+
*/
public static final String CT_FHIR_XML_NEW = "application/fhir+xml"; public static final String CT_FHIR_XML_NEW = "application/fhir+xml";
public static final String CT_HTML = "text/html"; public static final String CT_HTML = "text/html";
public static final String CT_HTML_WITH_UTF8 = "text/html" + CHARSET_UTF8_CTSUFFIX; public static final String CT_HTML_WITH_UTF8 = "text/html" + CHARSET_UTF8_CTSUFFIX;
@ -86,6 +92,7 @@ public class Constants {
public static final String HEADER_CONTENT_LOCATION = "Content-Location"; public static final String HEADER_CONTENT_LOCATION = "Content-Location";
public static final String HEADER_CONTENT_LOCATION_LC = HEADER_CONTENT_LOCATION.toLowerCase(); public static final String HEADER_CONTENT_LOCATION_LC = HEADER_CONTENT_LOCATION.toLowerCase();
public static final String HEADER_CONTENT_TYPE = "Content-Type"; public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_CONTENT_TYPE_LC = HEADER_CONTENT_TYPE.toLowerCase();
public static final String HEADER_COOKIE = "Cookie"; public static final String HEADER_COOKIE = "Cookie";
public static final String HEADER_CORS_ALLOW_METHODS = "Access-Control-Allow-Methods"; public static final String HEADER_CORS_ALLOW_METHODS = "Access-Control-Allow-Methods";
public static final String HEADER_CORS_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; public static final String HEADER_CORS_ALLOW_ORIGIN = "Access-Control-Allow-Origin";

View File

@ -20,11 +20,13 @@ package ca.uhn.fhir.rest.api;
* #L% * #L%
*/ */
import ca.uhn.fhir.util.CoverageIgnore;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.util.CoverageIgnore; import java.util.List;
import java.util.Map;
public class MethodOutcome { public class MethodOutcome {
@ -32,6 +34,7 @@ public class MethodOutcome {
private IIdType myId; private IIdType myId;
private IBaseOperationOutcome myOperationOutcome; private IBaseOperationOutcome myOperationOutcome;
private IBaseResource myResource; private IBaseResource myResource;
private Map<String, List<String>> myResponseHeaders;
/** /**
* Constructor * Constructor
@ -43,12 +46,9 @@ public class MethodOutcome {
/** /**
* Constructor * Constructor
* *
* @param theId * @param theId The ID of the created/updated resource
* The ID of the created/updated resource * @param theCreated If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
* * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
* @param theCreated
* If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
* whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
*/ */
@CoverageIgnore @CoverageIgnore
public MethodOutcome(IIdType theId, Boolean theCreated) { public MethodOutcome(IIdType theId, Boolean theCreated) {
@ -59,11 +59,8 @@ public class MethodOutcome {
/** /**
* Constructor * Constructor
* *
* @param theId * @param theId The ID of the created/updated resource
* The ID of the created/updated resource * @param theBaseOperationOutcome The operation outcome to return with the response (or null for none)
*
* @param theBaseOperationOutcome
* The operation outcome to return with the response (or null for none)
*/ */
public MethodOutcome(IIdType theId, IBaseOperationOutcome theBaseOperationOutcome) { public MethodOutcome(IIdType theId, IBaseOperationOutcome theBaseOperationOutcome) {
myId = theId; myId = theId;
@ -73,15 +70,10 @@ public class MethodOutcome {
/** /**
* Constructor * Constructor
* *
* @param theId * @param theId The ID of the created/updated resource
* The ID of the created/updated resource * @param theBaseOperationOutcome The operation outcome to return with the response (or null for none)
* * @param theCreated If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
* @param theBaseOperationOutcome * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
* The operation outcome to return with the response (or null for none)
*
* @param theCreated
* If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
* whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
*/ */
public MethodOutcome(IIdType theId, IBaseOperationOutcome theBaseOperationOutcome, Boolean theCreated) { public MethodOutcome(IIdType theId, IBaseOperationOutcome theBaseOperationOutcome, Boolean theCreated) {
myId = theId; myId = theId;
@ -92,8 +84,7 @@ public class MethodOutcome {
/** /**
* Constructor * Constructor
* *
* @param theId * @param theId The ID of the created/updated resource
* The ID of the created/updated resource
*/ */
public MethodOutcome(IIdType theId) { public MethodOutcome(IIdType theId) {
myId = theId; myId = theId;
@ -102,8 +93,7 @@ public class MethodOutcome {
/** /**
* Constructor * Constructor
* *
* @param theOperationOutcome * @param theOperationOutcome The operation outcome resource to return
* The operation outcome resource to return
*/ */
public MethodOutcome(IBaseOperationOutcome theOperationOutcome) { public MethodOutcome(IBaseOperationOutcome theOperationOutcome) {
myOperationOutcome = theOperationOutcome; myOperationOutcome = theOperationOutcome;
@ -117,10 +107,35 @@ public class MethodOutcome {
return myCreated; return myCreated;
} }
/**
* If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called whether the
* result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
* <p>
* Users of HAPI should only interact with this method in Server applications
* </p>
*
* @param theCreated If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
* whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public MethodOutcome setCreated(Boolean theCreated) {
myCreated = theCreated;
return this;
}
public IIdType getId() { public IIdType getId() {
return myId; return myId;
} }
/**
* @param theId The ID of the created/updated resource
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public MethodOutcome setId(IIdType theId) {
myId = theId;
return this;
}
/** /**
* Returns the {@link IBaseOperationOutcome} resource to return to the client or <code>null</code> if none. * Returns the {@link IBaseOperationOutcome} resource to return to the client or <code>null</code> if none.
* *
@ -130,6 +145,16 @@ public class MethodOutcome {
return myOperationOutcome; return myOperationOutcome;
} }
/**
* Sets the {@link IBaseOperationOutcome} resource to return to the client. Set to <code>null</code> (which is the default) if none.
*
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public MethodOutcome setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) {
myOperationOutcome = theBaseOperationOutcome;
return this;
}
/** /**
* <b>From a client response:</b> If the method returned an actual resource body (e.g. a create/update with * <b>From a client response:</b> If the method returned an actual resource body (e.g. a create/update with
* "Prefer: return=representation") this field will be populated with the * "Prefer: return=representation") this field will be populated with the
@ -139,42 +164,6 @@ public class MethodOutcome {
return myResource; return myResource;
} }
/**
* If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called whether the
* result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
* <p>
* Users of HAPI should only interact with this method in Server applications
* </p>
*
* @param theCreated
* If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
* whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public MethodOutcome setCreated(Boolean theCreated) {
myCreated = theCreated;
return this;
}
/**
* @param theId
* The ID of the created/updated resource
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public MethodOutcome setId(IIdType theId) {
myId = theId;
return this;
}
/**
* Sets the {@link IBaseOperationOutcome} resource to return to the client. Set to <code>null</code> (which is the default) if none.
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public MethodOutcome setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) {
myOperationOutcome = theBaseOperationOutcome;
return this;
}
/** /**
* <b>In a server response</b>: This field may be populated in server code with the final resource for operations * <b>In a server response</b>: This field may be populated in server code with the final resource for operations
* where a resource body is being created/updated. E.g. for an update method, this field could be populated with * where a resource body is being created/updated. E.g. for an update method, this field could be populated with
@ -183,6 +172,7 @@ public class MethodOutcome {
* This field is optional, but if it is populated the server will return the resource body if requested to * This field is optional, but if it is populated the server will return the resource body if requested to
* do so via the HTTP Prefer header. * do so via the HTTP Prefer header.
* </p> * </p>
*
* @return Returns a reference to <code>this</code> for easy method chaining * @return Returns a reference to <code>this</code> for easy method chaining
*/ */
public MethodOutcome setResource(IBaseResource theResource) { public MethodOutcome setResource(IBaseResource theResource) {
@ -190,4 +180,23 @@ public class MethodOutcome {
return this; return this;
} }
/**
* Gets the headers for the HTTP response
*/
public Map<String, List<String>> getResponseHeaders() {
return myResponseHeaders;
}
/**
* Sets the headers for the HTTP response
*/
public void setResponseHeaders(Map<String, List<String>> theResponseHeaders) {
myResponseHeaders = theResponseHeaders;
}
public void setCreatedUsingStatusCode(int theResponseStatusCode) {
if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
setCreated(true);
}
}
} }

View File

@ -31,13 +31,15 @@ public interface IHttpRequest {
/** /**
* Add a header to the request * Add a header to the request
* @param theName the header name *
* @param theName the header name
* @param theValue the header value * @param theValue the header value
*/ */
void addHeader(String theName, String theValue); void addHeader(String theName, String theValue);
/** /**
* Execute the request * Execute the request
*
* @return the response * @return the response
*/ */
IHttpResponse execute() throws IOException; IHttpResponse execute() throws IOException;
@ -51,6 +53,7 @@ public interface IHttpRequest {
/** /**
* Return the request body as a string. * Return the request body as a string.
* If this is not supported by the underlying technology, null is returned * If this is not supported by the underlying technology, null is returned
*
* @return a string representation of the request or null if not supported or empty. * @return a string representation of the request or null if not supported or empty.
*/ */
String getRequestBodyFromStream() throws IOException; String getRequestBodyFromStream() throws IOException;
@ -65,4 +68,10 @@ public interface IHttpRequest {
*/ */
String getHttpVerbName(); String getHttpVerbName();
/**
* Remove any headers matching the given name
*
* @param theHeaderName The header name, e.g. "Accept" (must not be null or blank)
*/
void removeHeaders(String theHeaderName);
} }

View File

@ -70,7 +70,7 @@ public interface IHttpResponse {
void close(); void close();
/** /**
* Returna reader for the response entity * Returns a reader for the response entity
*/ */
Reader createReader() throws IOException; Reader createReader() throws IOException;

View File

@ -19,15 +19,18 @@ package ca.uhn.fhir.rest.client.exceptions;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.IOException;
import java.io.Reader;
import org.apache.commons.io.IOUtils;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import static org.apache.commons.lang3.StringUtils.isBlank;
@CoverageIgnore @CoverageIgnore
public class NonFhirResponseException extends BaseServerResponseException { public class NonFhirResponseException extends BaseServerResponseException {
@ -37,23 +40,29 @@ public class NonFhirResponseException extends BaseServerResponseException {
/** /**
* Constructor * Constructor
* *
* @param theMessage * @param theMessage The message
* The message * @param theStatusCode The HTTP status code
* @param theResponseText
* @param theStatusCode
* @param theResponseReader
* @param theContentType
*/ */
NonFhirResponseException(int theStatusCode, String theMessage) { NonFhirResponseException(int theStatusCode, String theMessage) {
super(theStatusCode, theMessage); super(theStatusCode, theMessage);
} }
public static NonFhirResponseException newInstance(int theStatusCode, String theContentType, InputStream theInputStream) {
return newInstance(theStatusCode, theContentType, new InputStreamReader(theInputStream, Charsets.UTF_8));
}
public static NonFhirResponseException newInstance(int theStatusCode, String theContentType, Reader theReader) { public static NonFhirResponseException newInstance(int theStatusCode, String theContentType, Reader theReader) {
String responseBody = ""; String responseBody = "";
try { try {
responseBody = IOUtils.toString(theReader); responseBody = IOUtils.toString(theReader);
} catch (IOException e) { } catch (IOException e) {
IOUtils.closeQuietly(theReader); // ignore
} finally {
try {
theReader.close();
} catch (IOException theE) {
// ignore
}
} }
NonFhirResponseException retVal; NonFhirResponseException retVal;

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum;
import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.SummaryEnum;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -28,7 +29,7 @@ import java.util.List;
*/ */
public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> { public interface IClientExecutable<T extends IClientExecutable<?, Y>, Y> {
/** /**
* If set to true, the client will log the request and response to the SLF4J logger. This can be useful for * If set to true, the client will log the request and response to the SLF4J logger. This can be useful for
@ -52,10 +53,39 @@ public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> {
*/ */
T elementsSubset(String... theElements); T elementsSubset(String... theElements);
/**
* Request that the server respond with JSON via the Accept header and possibly also the
* <code>_format</code> parameter if {@link ca.uhn.fhir.rest.client.api.IRestfulClient#setFormatParamStyle(RequestFormatParamStyleEnum) configured to do so}.
* <p>
* This method will have no effect if {@link #accept(String) a custom Accept header} is specified.
* </p>
*
* @see #accept(String)
*/
T encoded(EncodingEnum theEncoding); T encoded(EncodingEnum theEncoding);
/**
* Request that the server respond with JSON via the Accept header and possibly also the
* <code>_format</code> parameter if {@link ca.uhn.fhir.rest.client.api.IRestfulClient#setFormatParamStyle(RequestFormatParamStyleEnum) configured to do so}.
* <p>
* This method will have no effect if {@link #accept(String) a custom Accept header} is specified.
* </p>
*
* @see #accept(String)
* @see #encoded(EncodingEnum)
*/
T encodedJson(); T encodedJson();
/**
* Request that the server respond with JSON via the Accept header and possibly also the
* <code>_format</code> parameter if {@link ca.uhn.fhir.rest.client.api.IRestfulClient#setFormatParamStyle(RequestFormatParamStyleEnum) configured to do so}.
* <p>
* This method will have no effect if {@link #accept(String) a custom Accept header} is specified.
* </p>
*
* @see #accept(String)
* @see #encoded(EncodingEnum)
*/
T encodedXml(); T encodedXml();
/** /**
@ -84,6 +114,9 @@ public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> {
*/ */
T preferResponseTypes(List<Class<? extends IBaseResource>> theTypes); T preferResponseTypes(List<Class<? extends IBaseResource>> theTypes);
/**
* Request pretty-printed response via the <code>_pretty</code> parameter
*/
T prettyPrint(); T prettyPrint();
/** /**
@ -91,4 +124,23 @@ public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> {
*/ */
T summaryMode(SummaryEnum theSummary); T summaryMode(SummaryEnum theSummary);
/**
* Specifies a custom <code>Accept</code> header that should be supplied with the
* request.
* <p>
* Note that this method overrides any encoding preferences specified with
* {@link #encodedJson()} or {@link #encodedXml()}. It is generally easier to
* just use those methods if you simply want to request a specific FHIR encoding.
* </p>
*
* @param theHeaderValue The header value, e.g. "application/fhir+json". Constants such
* as {@link ca.uhn.fhir.rest.api.Constants#CT_FHIR_XML_NEW} and
* {@link ca.uhn.fhir.rest.api.Constants#CT_FHIR_JSON_NEW} may
* be useful. If set to <code>null</code> or an empty string, the
* default Accept header will be used.
* @see #encoded(EncodingEnum)
* @see #encodedJson()
* @see #encodedXml()
*/
T accept(String theHeaderValue);
} }

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.gclient;
* #L% * #L%
*/ */
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
public interface IOperationUntypedWithInput<T> extends IClientExecutable<IOperationUntypedWithInput<T>, T> { public interface IOperationUntypedWithInput<T> extends IClientExecutable<IOperationUntypedWithInput<T>, T> {
@ -43,4 +44,9 @@ public interface IOperationUntypedWithInput<T> extends IClientExecutable<IOperat
*/ */
<R extends IBaseResource> IOperationUntypedWithInput<R> returnResourceType(Class<R> theReturnType); <R extends IBaseResource> IOperationUntypedWithInput<R> returnResourceType(Class<R> theReturnType);
/**
* Request that the method chain returns a {@link MethodOutcome} object. This object
* will contain details
*/
IOperationUntypedWithInput<MethodOutcome> returnMethodOutcome();
} }

View File

@ -100,7 +100,11 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ {
@Override @Override
String doGetValueAsQueryToken(FhirContext theContext) { String doGetValueAsQueryToken(FhirContext theContext) {
if (getSystem() != null) { if (getSystem() != null) {
return ParameterUtil.escape(StringUtils.defaultString(getSystem())) + '|' + ParameterUtil.escape(getValue()); if (getValue() != null) {
return ParameterUtil.escape(StringUtils.defaultString(getSystem())) + '|' + ParameterUtil.escape(getValue());
} else {
return ParameterUtil.escape(StringUtils.defaultString(getSystem())) + '|';
}
} }
return ParameterUtil.escape(getValue()); return ParameterUtil.escape(getValue());
} }

View File

@ -25,6 +25,7 @@ public enum VersionEnum {
V3_3_0, V3_3_0,
V3_4_0, V3_4_0,
V3_5_0, V3_5_0,
V3_6_0 V3_6_0,
V3_7_0
} }

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
@ -188,7 +188,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId> <artifactId>thymeleaf-spring5</artifactId>
</dependency> </dependency>
<!-- Dependencies for Schematron --> <!-- Dependencies for Schematron -->

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId> <artifactId>hapi-fhir-cli</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
@ -43,6 +43,14 @@
<classifier>classes</classifier> <classifier>classes</classifier>
</dependency> </dependency>
<!--
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojbc8g</artifactId>
<version>12.2.0.1</version>
</dependency>
-->
</dependencies> </dependencies>
<build> <build>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId> <artifactId>hapi-fhir-cli</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -94,4 +94,9 @@ public class OkHttpRestfulRequest implements IHttpRequest {
return myRequestTypeEnum.name(); return myRequestTypeEnum.name();
} }
@Override
public void removeHeaders(String theHeaderName) {
myRequestBuilder.removeHeader(theHeaderName);
}
} }

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -20,12 +20,11 @@ package ca.uhn.fhir.rest.client.apache;
* #L% * #L%
*/ */
import java.io.IOException; import ca.uhn.fhir.rest.client.api.IHttpRequest;
import java.nio.charset.Charset; import ca.uhn.fhir.rest.client.api.IHttpResponse;
import java.util.*;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpEntityEnclosingRequest;
@ -34,8 +33,9 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import ca.uhn.fhir.rest.client.api.IHttpRequest; import java.io.IOException;
import ca.uhn.fhir.rest.client.api.IHttpResponse; import java.nio.charset.Charset;
import java.util.*;
/** /**
* A Http Request based on Apache. This is an adapter around the class * A Http Request based on Apache. This is an adapter around the class
@ -79,6 +79,7 @@ public class ApacheHttpRequest implements IHttpRequest {
/** /**
* Get the ApacheRequest * Get the ApacheRequest
*
* @return the ApacheRequest * @return the ApacheRequest
*/ */
public HttpRequestBase getApacheRequest() { public HttpRequestBase getApacheRequest() {
@ -90,6 +91,12 @@ public class ApacheHttpRequest implements IHttpRequest {
return myRequest.getMethod(); return myRequest.getMethod();
} }
@Override
public void removeHeaders(String theHeaderName) {
Validate.notBlank(theHeaderName, "theHeaderName must not be null or blank");
myRequest.removeHeaders(theHeaderName);
}
@Override @Override
public String getRequestBodyFromStream() throws IOException { public String getRequestBodyFromStream() throws IOException {
if (myRequest instanceof HttpEntityEnclosingRequest) { if (myRequest instanceof HttpEntityEnclosingRequest) {

View File

@ -33,19 +33,20 @@ import ca.uhn.fhir.rest.client.method.IClientResponseHandler;
import ca.uhn.fhir.rest.client.method.IClientResponseHandlerHandlesBinary; import ca.uhn.fhir.rest.client.method.IClientResponseHandlerHandlesBinary;
import ca.uhn.fhir.rest.client.method.MethodUtil; import ca.uhn.fhir.rest.client.method.MethodUtil;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.BinaryUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.XmlDetectionUtil; import ca.uhn.fhir.util.XmlDetectionUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
import java.io.IOException; import java.io.*;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.*; import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseClient implements IRestfulClient { public abstract class BaseClient implements IRestfulClient {
@ -93,14 +94,16 @@ public abstract class BaseClient implements IRestfulClient {
} }
protected Map<String, List<String>> createExtraParams() { protected Map<String, List<String>> createExtraParams(String theCustomAcceptHeader) {
HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>(); HashMap<String, List<String>> retVal = new LinkedHashMap<>();
if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) { if (isBlank(theCustomAcceptHeader)) {
if (getEncoding() == EncodingEnum.XML) { if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml")); if (getEncoding() == EncodingEnum.XML) {
} else if (getEncoding() == EncodingEnum.JSON) { retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json")); } else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
} }
} }
@ -115,7 +118,7 @@ public abstract class BaseClient implements IRestfulClient {
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) { public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl); BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theResourceType); ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theResourceType);
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null); return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null, null);
} }
void forceConformanceCheck() { void forceConformanceCheck() {
@ -200,11 +203,11 @@ public abstract class BaseClient implements IRestfulClient {
} }
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) { <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null); return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null, null);
} }
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint,
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective) { boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective, String theCustomAcceptHeader) {
if (!myDontValidateConformance) { if (!myDontValidateConformance) {
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this); myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
@ -215,10 +218,10 @@ public abstract class BaseClient implements IRestfulClient {
IHttpRequest httpRequest = null; IHttpRequest httpRequest = null;
IHttpResponse response = null; IHttpResponse response = null;
try { try {
Map<String, List<String>> params = createExtraParams(); Map<String, List<String>> params = createExtraParams(theCustomAcceptHeader);
if (clientInvocation instanceof HttpGetClientInvocation) { if (clientInvocation instanceof HttpGetClientInvocation) {
if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) { if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT && isBlank(theCustomAcceptHeader)) {
if (theEncoding == EncodingEnum.XML) { if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml")); params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) { } else if (theEncoding == EncodingEnum.JSON) {
@ -248,6 +251,11 @@ public abstract class BaseClient implements IRestfulClient {
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint); httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
if (isNotBlank(theCustomAcceptHeader)) {
httpRequest.removeHeaders(Constants.HEADER_ACCEPT);
httpRequest.addHeader(Constants.HEADER_ACCEPT, theCustomAcceptHeader);
}
if (theCacheControlDirective != null) { if (theCacheControlDirective != null) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache()); addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache());
@ -289,14 +297,10 @@ public abstract class BaseClient implements IRestfulClient {
if (response.getStatus() < 200 || response.getStatus() > 299) { if (response.getStatus() < 200 || response.getStatus() > 299) {
String body = null; String body = null;
Reader reader = null; try (Reader reader = response.createReader()) {
try {
reader = response.createReader();
body = IOUtils.toString(reader); body = IOUtils.toString(reader);
} catch (Exception e) { } catch (Exception e) {
ourLog.debug("Failed to read input stream", e); ourLog.debug("Failed to read input stream", e);
} finally {
IOUtils.closeQuietly(reader);
} }
String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo(); String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo();
@ -334,27 +338,24 @@ public abstract class BaseClient implements IRestfulClient {
if (binding instanceof IClientResponseHandlerHandlesBinary) { if (binding instanceof IClientResponseHandlerHandlesBinary) {
IClientResponseHandlerHandlesBinary<T> handlesBinary = (IClientResponseHandlerHandlesBinary<T>) binding; IClientResponseHandlerHandlesBinary<T> handlesBinary = (IClientResponseHandlerHandlesBinary<T>) binding;
if (handlesBinary.isBinary()) { if (handlesBinary.isBinary()) {
InputStream reader = response.readEntity(); try (InputStream reader = response.readEntity()) {
try { return handlesBinary.invokeClientForBinary(mimeType, reader, response.getStatus(), headers);
return handlesBinary.invokeClient(mimeType, reader, response.getStatus(), headers);
} finally {
IOUtils.closeQuietly(reader);
} }
} }
} }
Reader reader = response.createReader(); try (InputStream inputStream = response.readEntity()) {
InputStream inputStreamToReturn = inputStream;
if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) { if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
String responseString = IOUtils.toString(reader); if (inputStream != null) {
keepResponseAndLogIt(theLogRequestAndResponse, response, responseString); String responseString = IOUtils.toString(inputStream, Charsets.UTF_8);
reader = new StringReader(responseString); keepResponseAndLogIt(theLogRequestAndResponse, response, responseString);
} inputStreamToReturn = new ByteArrayInputStream(responseString.getBytes(Charsets.UTF_8));
}
}
try { return binding.invokeClient(mimeType, inputStreamToReturn, response.getStatus(), headers);
return binding.invokeClient(mimeType, reader, response.getStatus(), headers);
} finally {
IOUtils.closeQuietly(reader);
} }
} catch (DataFormatException e) { } catch (DataFormatException e) {
@ -463,7 +464,48 @@ public abstract class BaseClient implements IRestfulClient {
myInterceptors.remove(theInterceptor); myInterceptors.remove(theInterceptor);
} }
protected final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> { protected final class ResourceOrBinaryResponseHandler extends ResourceResponseHandler<IBaseResource> {
@Override
public IBaseResource invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
/*
* For operation responses, if the response content type is a FHIR content-type
* (which is will probably almost always be) we just handle it normally. However,
* if we get back a successful (2xx) response from an operation, and the content
* type is something other than FHIR, we'll return it as a Binary wrapped in
* a Parameters resource.
*/
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType != null || theResponseStatusCode < 200 || theResponseStatusCode >= 300) {
return super.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
}
// Create a Binary resource to return
IBaseBinary responseBinary = BinaryUtil.newBinary(getFhirContext());
// Fetch the content type
String contentType = null;
List<String> contentTypeHeaders = theHeaders.get(Constants.HEADER_CONTENT_TYPE_LC);
if (contentTypeHeaders != null && contentTypeHeaders.size() > 0) {
contentType = contentTypeHeaders.get(0);
}
responseBinary.setContentType(contentType);
// Fetch the content itself
try {
responseBinary.setContent(IOUtils.toByteArray(theResponseInputStream));
} catch (IOException e) {
throw new InternalErrorException("IO failure parsing response", e);
}
return responseBinary;
}
}
protected class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
private boolean myAllowHtmlResponse; private boolean myAllowHtmlResponse;
private IIdType myId; private IIdType myId;
@ -498,20 +540,20 @@ public abstract class BaseClient implements IRestfulClient {
} }
@Override @Override
public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) { if (respType == null) {
if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) { if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) {
return readHtmlResponse(theResponseReader); return readHtmlResponse(theResponseInputStream);
} }
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
} }
IParser parser = respType.newParser(getFhirContext()); IParser parser = respType.newParser(getFhirContext());
parser.setServerBaseUrl(getUrlBase()); parser.setServerBaseUrl(getUrlBase());
if (myPreferResponseTypes != null) { if (myPreferResponseTypes != null) {
parser.setPreferTypes(myPreferResponseTypes); parser.setPreferTypes(myPreferResponseTypes);
} }
T retVal = parser.parseResource(myReturnType, theResponseReader); T retVal = parser.parseResource(myReturnType, theResponseInputStream);
MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal); MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
@ -519,7 +561,7 @@ public abstract class BaseClient implements IRestfulClient {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private T readHtmlResponse(Reader theResponseReader) { private T readHtmlResponse(InputStream theResponseInputStream) {
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType); RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType);
IBaseResource instance = resDef.newInstance(); IBaseResource instance = resDef.newInstance();
BaseRuntimeChildDefinition textChild = resDef.getChildByName("text"); BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
@ -531,7 +573,7 @@ public abstract class BaseClient implements IRestfulClient {
BaseRuntimeElementDefinition<?> divElement = divChild.getChildByName("div"); BaseRuntimeElementDefinition<?> divElement = divChild.getChildByName("div");
IPrimitiveType<?> divInstance = (IPrimitiveType<?>) divElement.newInstance(); IPrimitiveType<?> divInstance = (IPrimitiveType<?>) divElement.newInstance();
try { try {
divInstance.setValueAsString(IOUtils.toString(theResponseReader)); divInstance.setValueAsString(IOUtils.toString(theResponseInputStream, Charsets.UTF_8));
} catch (Exception e) { } catch (Exception e) {
throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e); throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e);
} }
@ -539,8 +581,9 @@ public abstract class BaseClient implements IRestfulClient {
return (T) instance; return (T) instance;
} }
public void setPreferResponseTypes(List<Class<? extends IBaseResource>> thePreferResponseTypes) { public ResourceResponseHandler<T> setPreferResponseTypes(List<Class<? extends IBaseResource>> thePreferResponseTypes) {
myPreferResponseTypes = thePreferResponseTypes; myPreferResponseTypes = thePreferResponseTypes;
return this;
} }
} }

View File

@ -46,19 +46,19 @@ import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.util.ICallable; import ca.uhn.fhir.util.ICallable;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/** /**
* @author James Agnew * @author James Agnew
@ -98,7 +98,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IIdType theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint, private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IIdType theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint,
SummaryEnum theSummary, EncodingEnum theEncoding, Set<String> theSubsetElements) { SummaryEnum theSummary, EncodingEnum theEncoding, Set<String> theSubsetElements, String theCustomAcceptHeaderValue) {
String resName = toResourceName(theType); String resName = toResourceName(theType);
IIdType id = theId; IIdType id = theId;
if (!id.hasBaseUrl()) { if (!id.hasBaseUrl()) {
@ -120,7 +120,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
} }
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(theCustomAcceptHeaderValue), getEncoding(), isPrettyPrint());
} }
if (theIfVersionMatches != null) { if (theIfVersionMatches != null) {
@ -131,10 +131,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
ResourceResponseHandler<T> binding = new ResourceResponseHandler<>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse); ResourceResponseHandler<T> binding = new ResourceResponseHandler<>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse);
if (theNotModifiedHandler == null) { if (theNotModifiedHandler == null) {
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null); return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue);
} }
try { try {
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null); return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue);
} catch (NotModifiedException e) { } catch (NotModifiedException e) {
return theNotModifiedHandler.call(); return theNotModifiedHandler.call();
} }
@ -228,7 +228,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override @Override
public <T extends IBaseResource> T read(final Class<T> theType, UriDt theUrl) { public <T extends IBaseResource> T read(final Class<T> theType, UriDt theUrl) {
IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl); IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl);
return doReadOrVRead(theType, id, false, null, null, false, null, null, null); return doReadOrVRead(theType, id, false, null, null, false, null, null, null, null);
} }
@Override @Override
@ -269,7 +269,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public MethodOutcome update(IdDt theIdDt, IBaseResource theResource) { public MethodOutcome update(IdDt theIdDt, IBaseResource theResource) {
BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext); BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext);
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(null), getEncoding(), isPrettyPrint());
} }
OutcomeResponseHandler binding = new OutcomeResponseHandler(); OutcomeResponseHandler binding = new OutcomeResponseHandler();
@ -293,7 +293,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
invocation = ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, theResource); invocation = ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, theResource);
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(null), getEncoding(), isPrettyPrint());
} }
OutcomeResponseHandler binding = new OutcomeResponseHandler(); OutcomeResponseHandler binding = new OutcomeResponseHandler();
@ -306,7 +306,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
if (theId.hasVersionIdPart() == false) { if (theId.hasVersionIdPart() == false) {
throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue())); throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue()));
} }
return doReadOrVRead(theType, theId, true, null, null, false, null, null, null); return doReadOrVRead(theType, theId, true, null, null, false, null, null, null, null);
} }
@Override @Override
@ -315,49 +315,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return vread(theType, resId); return vread(theType, resId);
} }
private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
if (!params.containsKey(parameterName)) {
params.put(parameterName, new ArrayList<>());
}
params.get(parameterName).add(parameterValue);
}
private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) {
if (thePrefer != null) {
theInvocation.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue());
}
}
private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
StringBuilder b = new StringBuilder();
boolean haveHadQuestionMark = false;
for (int i = 0; i < theSearchUrl.length(); i++) {
char nextChar = theSearchUrl.charAt(i);
if (!haveHadQuestionMark) {
if (nextChar == '?') {
haveHadQuestionMark = true;
} else if (!Character.isLetter(nextChar)) {
throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl);
}
b.append(nextChar);
} else {
switch (nextChar) {
case '|':
case '?':
case '$':
case ':':
b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar)));
break;
default:
b.append(nextChar);
break;
}
}
}
return b.toString();
}
private enum MetaOperation { private enum MetaOperation {
ADD, ADD,
DELETE, DELETE,
@ -366,14 +323,25 @@ public class GenericClient extends BaseClient implements IGenericClient {
private abstract class BaseClientExecutable<T extends IClientExecutable<?, Y>, Y> implements IClientExecutable<T, Y> { private abstract class BaseClientExecutable<T extends IClientExecutable<?, Y>, Y> implements IClientExecutable<T, Y> {
protected EncodingEnum myParamEncoding; EncodingEnum myParamEncoding;
protected Boolean myPrettyPrint; Boolean myPrettyPrint;
protected SummaryEnum mySummaryMode; SummaryEnum mySummaryMode;
protected CacheControlDirective myCacheControlDirective; CacheControlDirective myCacheControlDirective;
private String myCustomAcceptHeaderValue;
private List<Class<? extends IBaseResource>> myPreferResponseTypes; private List<Class<? extends IBaseResource>> myPreferResponseTypes;
private boolean myQueryLogRequestAndResponse; private boolean myQueryLogRequestAndResponse;
private HashSet<String> mySubsetElements; private HashSet<String> mySubsetElements;
public String getCustomAcceptHeaderValue() {
return myCustomAcceptHeaderValue;
}
@Override
public T accept(String theHeaderValue) {
myCustomAcceptHeaderValue = theHeaderValue;
return (T) this;
}
@Deprecated // override deprecated method @Deprecated // override deprecated method
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
@ -392,7 +360,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override @Override
public T elementsSubset(String... theElements) { public T elementsSubset(String... theElements) {
if (theElements != null && theElements.length > 0) { if (theElements != null && theElements.length > 0) {
mySubsetElements = new HashSet<String>(Arrays.asList(theElements)); mySubsetElements = new HashSet<>(Arrays.asList(theElements));
} else { } else {
mySubsetElements = null; mySubsetElements = null;
} }
@ -444,7 +412,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint); myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint);
} }
Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective); Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective, myCustomAcceptHeaderValue);
return resp; return resp;
} }
@ -461,7 +429,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public T preferResponseType(Class<? extends IBaseResource> theClass) { public T preferResponseType(Class<? extends IBaseResource> theClass) {
myPreferResponseTypes = null; myPreferResponseTypes = null;
if (theClass != null) { if (theClass != null) {
myPreferResponseTypes = new ArrayList<Class<? extends IBaseResource>>(); myPreferResponseTypes = new ArrayList<>();
myPreferResponseTypes.add(theClass); myPreferResponseTypes.add(theClass);
} }
return (T) this; return (T) this;
@ -1021,14 +989,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) { if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
} }
IParser parser = respType.newParser(myContext); IParser parser = respType.newParser(myContext);
RuntimeResourceDefinition type = myContext.getResourceDefinition("Parameters"); RuntimeResourceDefinition type = myContext.getResourceDefinition("Parameters");
IBaseResource retVal = parser.parseResource(type.getImplementingClass(), theResponseReader); IBaseResource retVal = parser.parseResource(type.getImplementingClass(), theResponseInputStream);
BaseRuntimeChildDefinition paramChild = type.getChildByName("parameter"); BaseRuntimeChildDefinition paramChild = type.getChildByName("parameter");
BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter"); BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
@ -1061,6 +1029,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
private Class myReturnResourceType; private Class myReturnResourceType;
private Class<? extends IBaseResource> myType; private Class<? extends IBaseResource> myType;
private boolean myUseHttpGet; private boolean myUseHttpGet;
private boolean myReturnMethodOutcome;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void addParam(String theName, IBase theValue) { private void addParam(String theName, IBase theValue) {
@ -1170,11 +1139,19 @@ public class GenericClient extends BaseClient implements IGenericClient {
Object retVal = invoke(null, handler, invocation); Object retVal = invoke(null, handler, invocation);
return retVal; return retVal;
} }
ResourceResponseHandler handler; IClientResponseHandler handler = new ResourceOrBinaryResponseHandler()
handler = new ResourceResponseHandler(); .setPreferResponseTypes(getPreferResponseTypes(myType));
handler.setPreferResponseTypes(getPreferResponseTypes(myType));
if (myReturnMethodOutcome) {
handler = new MethodOutcomeResponseHandler(handler);
}
Object retVal = invoke(null, handler, invocation); Object retVal = invoke(null, handler, invocation);
if (myReturnMethodOutcome) {
return retVal;
}
if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) { if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) {
return retVal; return retVal;
} }
@ -1236,6 +1213,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this; return this;
} }
@Override
public IOperationUntypedWithInput returnMethodOutcome() {
myReturnMethodOutcome = true;
return this;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public IOperationProcessMsgMode setMessageBundle(IBaseBundle theMsgBundle) { public IOperationProcessMsgMode setMessageBundle(IBaseBundle theMsgBundle) {
@ -1331,10 +1314,30 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
private final class MethodOutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
private final IClientResponseHandler<? extends IBaseResource> myWrap;
private MethodOutcomeResponseHandler(IClientResponseHandler<? extends IBaseResource> theWrap) {
myWrap = theWrap;
}
@Override
public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
IBaseResource response = myWrap.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
MethodOutcome retVal = new MethodOutcome();
retVal.setResource(response);
retVal.setCreatedUsingStatusCode(theResponseStatusCode);
retVal.setResponseHeaders(theHeaders);
return retVal;
}
}
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<IBaseOperationOutcome> { private final class OperationOutcomeResponseHandler implements IClientResponseHandler<IBaseOperationOutcome> {
@Override @Override
public IBaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) public IBaseOperationOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders)
throws BaseServerResponseException { throws BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) { if (respType == null) {
@ -1344,7 +1347,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
IBaseOperationOutcome retVal; IBaseOperationOutcome retVal;
try { try {
// TODO: handle if something else than OO comes back // TODO: handle if something else than OO comes back
retVal = (IBaseOperationOutcome) parser.parseResource(theResponseReader); retVal = (IBaseOperationOutcome) parser.parseResource(theResponseInputStream);
} catch (DataFormatException e) { } catch (DataFormatException e) {
ourLog.warn("Failed to parse OperationOutcome response", e); ourLog.warn("Failed to parse OperationOutcome response", e);
return null; return null;
@ -1368,11 +1371,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
@Override @Override
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
MethodOutcome response = MethodUtil.process2xxResponse(myContext, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders); MethodOutcome response = MethodUtil.process2xxResponse(myContext, theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders);
if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) { response.setCreatedUsingStatusCode(theResponseStatusCode);
response.setCreated(true);
}
if (myPrefer == PreferReturnEnum.REPRESENTATION) { if (myPrefer == PreferReturnEnum.REPRESENTATION) {
if (response.getResource() == null) { if (response.getResource() == null) {
@ -1384,6 +1385,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
} }
response.setResponseHeaders(theHeaders);
return response; return response;
} }
} }
@ -1511,9 +1514,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override @Override
public Object execute() {// AAA public Object execute() {// AAA
if (myId.hasVersionIdPart()) { if (myId.hasVersionIdPart()) {
return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements()); return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue());
} }
return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements()); return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue());
} }
@Override @Override
@ -1636,11 +1639,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<IBaseResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) public List<IBaseResource> invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders)
throws BaseServerResponseException { throws BaseServerResponseException {
Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass(); Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass();
ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<IBaseResource>((Class<IBaseResource>) bundleType); ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<>((Class<IBaseResource>) bundleType);
IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders); IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory(); IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory();
bundleFactory.initializeWithBundleResource(response); bundleFactory.initializeWithBundleResource(response);
return bundleFactory.toListOfResources(); return bundleFactory.toListOfResources();
@ -1937,9 +1940,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class StringResponseHandler implements IClientResponseHandler<String> { private final class StringResponseHandler implements IClientResponseHandler<String> {
@Override @Override
public String invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) public String invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders)
throws IOException, BaseServerResponseException { throws IOException, BaseServerResponseException {
return IOUtils.toString(theResponseReader); return IOUtils.toString(theResponseInputStream, Charsets.UTF_8);
} }
} }
@ -2251,4 +2254,47 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
if (!params.containsKey(parameterName)) {
params.put(parameterName, new ArrayList<>());
}
params.get(parameterName).add(parameterValue);
}
private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) {
if (thePrefer != null) {
theInvocation.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue());
}
}
private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
StringBuilder b = new StringBuilder();
boolean haveHadQuestionMark = false;
for (int i = 0; i < theSearchUrl.length(); i++) {
char nextChar = theSearchUrl.charAt(i);
if (!haveHadQuestionMark) {
if (nextChar == '?') {
haveHadQuestionMark = true;
} else if (!Character.isLetter(nextChar)) {
throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl);
}
b.append(nextChar);
} else {
switch (nextChar) {
case '|':
case '?':
case '$':
case ':':
b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar)));
break;
default:
b.append(nextChar);
break;
}
}
}
return b.toString();
}
} }

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.method;
*/ */
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
@ -71,11 +72,11 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
} }
protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, List<Class<? extends IBaseResource>> thePreferTypes) { protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, List<Class<? extends IBaseResource>> thePreferTypes) {
EncodingEnum encoding = EncodingEnum.forContentType(theResponseMimeType); EncodingEnum encoding = EncodingEnum.forContentType(theResponseMimeType);
if (encoding == null) { if (encoding == null) {
NonFhirResponseException ex = NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); NonFhirResponseException ex = NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
populateException(ex, theResponseReader); populateException(ex, theResponseInputStream);
throw ex; throw ex;
} }
@ -139,7 +140,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
return mySupportsConditionalMultiple; return mySupportsConditionalMultiple;
} }
protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, Reader theResponseReader) { protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, InputStream theResponseInputStream) {
BaseServerResponseException ex; BaseServerResponseException ex;
switch (theStatusCode) { switch (theStatusCode) {
case Constants.STATUS_HTTP_400_BAD_REQUEST: case Constants.STATUS_HTTP_400_BAD_REQUEST:
@ -158,9 +159,9 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
ex = new PreconditionFailedException("Server responded with HTTP 412"); ex = new PreconditionFailedException("Server responded with HTTP 412");
break; break;
case Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY: case Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY:
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theStatusCode, null); IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseInputStream, theStatusCode, null);
// TODO: handle if something other than OO comes back // TODO: handle if something other than OO comes back
BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseReader); BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseInputStream);
ex = new UnprocessableEntityException(myContext, operationOutcome); ex = new UnprocessableEntityException(myContext, operationOutcome);
break; break;
default: default:
@ -168,7 +169,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
break; break;
} }
populateException(ex, theResponseReader); populateException(ex, theResponseInputStream);
return ex; return ex;
} }
@ -322,9 +323,9 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
return theReturnTypeFromMethod.equals(IBaseResource.class) || theReturnTypeFromMethod.equals(IResource.class) || theReturnTypeFromMethod.equals(IAnyResource.class); return theReturnTypeFromMethod.equals(IBaseResource.class) || theReturnTypeFromMethod.equals(IResource.class) || theReturnTypeFromMethod.equals(IAnyResource.class);
} }
private static void populateException(BaseServerResponseException theEx, Reader theResponseReader) { private static void populateException(BaseServerResponseException theEx, InputStream theResponseInputStream) {
try { try {
String responseText = IOUtils.toString(theResponseReader); String responseText = IOUtils.toString(theResponseInputStream);
theEx.setResponseBody(responseText); theEx.setResponseBody(responseText);
} catch (IOException e) { } catch (IOException e) {
ourLog.debug("Failed to read response", e); ourLog.debug("Failed to read response", e);

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.client.method;
* #L% * #L%
*/ */
import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
@ -68,15 +69,15 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
protected abstract String getMatchingOperation(); protected abstract String getMatchingOperation();
@Override @Override
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
if (theResponseStatusCode >= 200 && theResponseStatusCode < 300) { if (theResponseStatusCode >= 200 && theResponseStatusCode < 300) {
if (myReturnVoid) { if (myReturnVoid) {
return null; return null;
} }
MethodOutcome retVal = MethodUtil.process2xxResponse(getContext(), theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders); MethodOutcome retVal = MethodUtil.process2xxResponse(getContext(), theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders);
return retVal; return retVal;
} }
throw processNon2xxResponseAndReturnExceptionToThrow(theResponseStatusCode, theResponseMimeType, theResponseReader); throw processNon2xxResponseAndReturnExceptionToThrow(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
} }
public boolean isReturnVoid() { public boolean isReturnVoid() {

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.client.method;
* #L% * #L%
*/ */
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
@ -123,21 +125,21 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
public abstract ReturnTypeEnum getReturnType(); public abstract ReturnTypeEnum getReturnType();
@Override @Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) { public Object invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException {
if (Constants.STATUS_HTTP_204_NO_CONTENT == theResponseStatusCode) { if (Constants.STATUS_HTTP_204_NO_CONTENT == theResponseStatusCode) {
return toReturnType(null); return toReturnType(null);
} }
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theResponseStatusCode, myPreferTypesList); IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseInputStream, theResponseStatusCode, myPreferTypesList);
switch (getReturnType()) { switch (getReturnType()) {
case BUNDLE: { case BUNDLE: {
IBaseBundle bundle = null; IBaseBundle bundle;
List<? extends IBaseResource> listOfResources = null; List<? extends IBaseResource> listOfResources;
Class<? extends IBaseResource> type = getContext().getResourceDefinition("Bundle").getImplementingClass(); Class<? extends IBaseResource> type = getContext().getResourceDefinition("Bundle").getImplementingClass();
bundle = (IBaseBundle) parser.parseResource(type, theResponseReader); bundle = (IBaseBundle) parser.parseResource(type, theResponseInputStream);
listOfResources = BundleUtil.toListOfResources(getContext(), bundle); listOfResources = BundleUtil.toListOfResources(getContext(), bundle);
switch (getMethodReturnType()) { switch (getMethodReturnType()) {
@ -171,9 +173,9 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
case RESOURCE: { case RESOURCE: {
IBaseResource resource; IBaseResource resource;
if (myResourceType != null) { if (myResourceType != null) {
resource = parser.parseResource(myResourceType, theResponseReader); resource = parser.parseResource(myResourceType, theResponseInputStream);
} else { } else {
resource = parser.parseResource(theResponseReader); resource = parser.parseResource(theResponseInputStream);
} }
MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, resource); MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, resource);

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.method;
*/ */
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -29,6 +30,6 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public interface IClientResponseHandler<T> { public interface IClientResponseHandler<T> {
T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException; T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException;
} }

View File

@ -35,6 +35,6 @@ public interface IClientResponseHandlerHandlesBinary<T> extends IClientResponseH
*/ */
boolean isBinary(); boolean isBinary();
T invokeClient(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException; T invokeClientForBinary(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException;
} }

View File

@ -49,15 +49,6 @@ import ca.uhn.fhir.util.*;
public class MethodUtil { public class MethodUtil {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class);
private static final Set<String> ourServletRequestTypes = new HashSet<String>();
private static final Set<String> ourServletResponseTypes = new HashSet<String>();
static {
ourServletRequestTypes.add("javax.servlet.ServletRequest");
ourServletResponseTypes.add("javax.servlet.ServletResponse");
ourServletRequestTypes.add("javax.servlet.http.HttpServletRequest");
ourServletResponseTypes.add("javax.servlet.http.HttpServletResponse");
}
/** Non instantiable */ /** Non instantiable */
private MethodUtil() { private MethodUtil() {
@ -497,8 +488,8 @@ public class MethodUtil {
} }
public static MethodOutcome process2xxResponse(FhirContext theContext, int theResponseStatusCode, public static MethodOutcome process2xxResponse(FhirContext theContext, int theResponseStatusCode,
String theResponseMimeType, Reader theResponseReader, Map<String, List<String>> theHeaders) { String theResponseMimeType, InputStream theResponseReader, Map<String, List<String>> theHeaders) {
List<String> locationHeaders = new ArrayList<String>(); List<String> locationHeaders = new ArrayList<>();
List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC); List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC);
if (lh != null) { if (lh != null) {
locationHeaders.addAll(lh); locationHeaders.addAll(lh);
@ -509,14 +500,14 @@ public class MethodUtil {
} }
MethodOutcome retVal = new MethodOutcome(); MethodOutcome retVal = new MethodOutcome();
if (locationHeaders != null && locationHeaders.size() > 0) { if (locationHeaders.size() > 0) {
String locationHeader = locationHeaders.get(0); String locationHeader = locationHeaders.get(0);
BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, locationHeader); BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, locationHeader);
} }
if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) { if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType); EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType);
if (ct != null) { if (ct != null) {
PushbackReader reader = new PushbackReader(theResponseReader); PushbackInputStream reader = new PushbackInputStream(theResponseReader);
try { try {
int firstByte = reader.read(); int firstByte = reader.read();

View File

@ -106,7 +106,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
} }
@Override @Override
public Object invokeClient(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) public Object invokeClientForBinary(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders)
throws IOException, BaseServerResponseException { throws IOException, BaseServerResponseException {
byte[] contents = IOUtils.toByteArray(theResponseReader); byte[] contents = IOUtils.toByteArray(theResponseReader);

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
@ -16,14 +16,14 @@
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId> <artifactId>hapi-fhir-base</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
</dependency> </dependency>
<!-- Server --> <!-- Server -->
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-server</artifactId> <artifactId>hapi-fhir-server</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
@ -35,43 +35,43 @@
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId> <artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId> <artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2.1</artifactId> <artifactId>hapi-fhir-structures-dstu2.1</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId> <artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r4</artifactId> <artifactId>hapi-fhir-structures-r4</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId> <artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu3</artifactId> <artifactId>hapi-fhir-validation-resources-dstu3</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -11,7 +11,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -85,6 +85,11 @@ public class JaxRsHttpRequest implements IHttpRequest {
return myRequestType.name(); return myRequestType.name();
} }
@Override
public void removeHeaders(String theHeaderName) {
myHeaders.remove(theHeaderName);
}
/** /**
* Get the Request * Get the Request
* *

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jaxrs.client; package ca.uhn.fhir.jaxrs.client;
import java.io.IOException; import java.io.*;
/* /*
* #%L * #%L
@ -22,9 +22,6 @@ import java.io.IOException;
* #L% * #L%
*/ */
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -34,9 +31,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import ca.uhn.fhir.rest.client.impl.BaseHttpResponse; import ca.uhn.fhir.rest.client.impl.BaseHttpResponse;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.client.api.IHttpResponse;
import org.apache.commons.io.IOUtils;
/** /**
* A Http Response based on JaxRs. This is an adapter around the class {@link javax.ws.rs.core.Response Response} * A Http Response based on JaxRs. This is an adapter around the class {@link javax.ws.rs.core.Response Response}
@ -118,7 +117,11 @@ public class JaxRsHttpResponse extends BaseHttpResponse implements IHttpResponse
@Override @Override
public InputStream readEntity() { public InputStream readEntity() {
return myResponse.readEntity(java.io.InputStream.class); if (!myBufferedEntity && !myResponse.hasEntity()) {
return new ByteArrayInputStream(new byte[0]);
} else {
return new ByteArrayInputStream(myResponse.readEntity(byte[].class));
}
} }
@Override @Override

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0</version> <version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
@ -163,7 +163,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId> <artifactId>thymeleaf-spring5</artifactId>
</dependency> </dependency>
<!-- For UCUM: TODO we should replace this with org.fhir UCUM --> <!-- For UCUM: TODO we should replace this with org.fhir UCUM -->
@ -670,7 +670,7 @@
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration> <configuration>
<runOrder>alphabetical</runOrder> <runOrder>alphabetical</runOrder>
<argLine>@{argLine} -Dfile.encoding=UTF-8 -Xmx1024m</argLine> <argLine>@{argLine} -Dfile.encoding=UTF-8 -Xmx20484M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC</argLine>
<forkCount>0.6C</forkCount> <forkCount>0.6C</forkCount>
</configuration> </configuration>
</plugin> </plugin>

View File

@ -1,5 +1,35 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import javax.annotation.Nonnull;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -20,46 +50,14 @@ package ca.uhn.fhir.jpa.config;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.search.*;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import javax.annotation.Nonnull;
import java.util.concurrent.ScheduledExecutorService;
@Configuration @Configuration
@EnableScheduling @EnableScheduling
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
@ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters={
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=BaseConfig.class),
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=WebSocketConfigurer.class)})
public abstract class BaseConfig implements SchedulingConfigurer { public abstract class BaseConfig implements SchedulingConfigurer {
public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor"; public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor";
@ -67,11 +65,6 @@ public abstract class BaseConfig implements SchedulingConfigurer {
@Autowired @Autowired
protected Environment myEnv; protected Environment myEnv;
@Bean(name = "myDaoRegistry")
public DaoRegistry daoRegistry() {
return new DaoRegistry();
}
@Override @Override
public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) { public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) {
theTaskRegistrar.setTaskScheduler(taskScheduler()); theTaskRegistrar.setTaskScheduler(taskScheduler());
@ -95,27 +88,12 @@ public abstract class BaseConfig implements SchedulingConfigurer {
public abstract FhirContext fhirContext(); public abstract FhirContext fhirContext();
@Bean
public ICacheWarmingSvc cacheWarmingSvc() {
return new CacheWarmingSvcImpl();
}
@Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
@Bean
public HibernateJpaDialect hibernateJpaDialectInstance() {
return new HibernateJpaDialect();
}
@Bean() @Bean()
public ScheduledExecutorService scheduledExecutorService() { public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean(); ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
b.setPoolSize(5); b.setPoolSize(5);
b.afterPropertiesSet(); b.afterPropertiesSet();
return b.getObject(); return b;
} }
@Bean(name = "mySubscriptionTriggeringProvider") @Bean(name = "mySubscriptionTriggeringProvider")
@ -124,17 +102,28 @@ public abstract class BaseConfig implements SchedulingConfigurer {
return new SubscriptionTriggeringProvider(); return new SubscriptionTriggeringProvider();
} }
@Bean(autowire = Autowire.BY_TYPE, name = "mySearchCoordinatorSvc") @Bean()
public ISearchCoordinatorSvc searchCoordinatorSvc() { public TaskScheduler taskScheduler() {
return new SearchCoordinatorSvcImpl(); ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
retVal.setConcurrentExecutor(scheduledExecutorService().getObject());
retVal.setScheduledExecutor(scheduledExecutorService().getObject());
return retVal;
}
@Bean(name = TASK_EXECUTOR_NAME)
public AsyncTaskExecutor taskExecutor() {
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
retVal.setConcurrentExecutor(scheduledExecutorService().getObject());
retVal.setScheduledExecutor(scheduledExecutorService().getObject());
return retVal;
} }
@Bean @Bean
public ISearchParamPresenceSvc searchParamPresenceSvc() { public IResourceReindexingSvc resourceReindexingSvc() {
return new SearchParamPresenceSvcImpl(); return new ResourceReindexingSvcImpl();
} }
@Bean(autowire = Autowire.BY_TYPE) @Bean
public IStaleSearchDeletingSvc staleSearchDeletingSvc() { public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
return new StaleSearchDeletingSvcImpl(); return new StaleSearchDeletingSvcImpl();
} }
@ -162,19 +151,6 @@ public abstract class BaseConfig implements SchedulingConfigurer {
return new SubscriptionWebsocketInterceptor(); return new SubscriptionWebsocketInterceptor();
} }
@Bean(name = TASK_EXECUTOR_NAME)
public TaskScheduler taskScheduler() {
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
retVal.setConcurrentExecutor(scheduledExecutorService());
retVal.setScheduledExecutor(scheduledExecutorService());
return retVal;
}
@Bean
public IResourceReindexingSvc resourceReindexingSvc() {
return new ResourceReindexingSvcImpl();
}
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) { public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer())); theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.entity"); theFactory.setPackagesToScan("ca.uhn.fhir.jpa.entity");
@ -184,13 +160,4 @@ public abstract class BaseConfig implements SchedulingConfigurer {
private static HibernateJpaDialect hibernateJpaDialect(HapiLocalizer theLocalizer) { private static HibernateJpaDialect hibernateJpaDialect(HapiLocalizer theLocalizer) {
return new HapiFhirHibernateJpaDialect(theLocalizer); return new HapiFhirHibernateJpaDialect(theLocalizer);
} }
/**
* This lets the "@Value" fields reference properties from the properties file
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
} }

View File

@ -1,126 +1,11 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import ca.uhn.fhir.context.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
import java.io.CharArrayWriter;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.data.*;
import org.apache.commons.lang3.NotImplementedException; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hibernate.Session;
import org.hibernate.internal.SessionImpl;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.BaseResource;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Sets;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
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.index.IndexingSupport;
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.dao.index.SearchParamExtractorService;
import ca.uhn.fhir.jpa.entity.BaseTag; import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.ResourceTag;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchInclude;
import ca.uhn.fhir.jpa.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.entity.SearchResult;
import ca.uhn.fhir.jpa.entity.SearchStatusEnum;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermConceptMap;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.entity.TermConceptProperty;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
@ -129,8 +14,6 @@ import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.Tag;
@ -145,25 +28,11 @@ import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriAndListParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
@ -172,6 +41,45 @@ import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.XmlUtil; import ca.uhn.fhir.util.XmlUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.Validate;
import org.hibernate.Session;
import org.hibernate.internal.SessionImpl;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
import java.io.CharArrayWriter;
import java.text.Normalizer;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import static org.apache.commons.lang3.StringUtils.*;
/* /*
* #%L * #%L
@ -195,7 +103,7 @@ import ca.uhn.fhir.util.XmlUtil;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@Repository @Repository
public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao, ApplicationContextAware, IndexingSupport { public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao, ApplicationContextAware {
public static final long INDEX_STATUS_INDEXED = 1L; public static final long INDEX_STATUS_INDEXED = 1L;
public static final long INDEX_STATUS_INDEXING_FAILED = 2L; public static final long INDEX_STATUS_INDEXING_FAILED = 2L;
@ -204,46 +112,17 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
public static final String OO_SEVERITY_INFO = "information"; public static final String OO_SEVERITY_INFO = "information";
public static final String OO_SEVERITY_WARN = "warning"; public static final String OO_SEVERITY_WARN = "warning";
public static final String UCUM_NS = "http://unitsofmeasure.org"; public static final String UCUM_NS = "http://unitsofmeasure.org";
static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/
static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/
static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>(); private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest"; private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
private static boolean ourValidationDisabledForUnitTest; private static boolean ourValidationDisabledForUnitTest;
private static boolean ourDisableIncrementOnUpdateForUnitTest = false; private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
static {
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>();
resourceMetaParams.put(BaseResource.SP_RES_ID, StringParam.class);
resourceMetaAndParams.put(BaseResource.SP_RES_ID, StringAndListParam.class);
resourceMetaParams.put(BaseResource.SP_RES_LANGUAGE, StringParam.class);
resourceMetaAndParams.put(BaseResource.SP_RES_LANGUAGE, StringAndListParam.class);
resourceMetaParams.put(Constants.PARAM_TAG, TokenParam.class);
resourceMetaAndParams.put(Constants.PARAM_TAG, TokenAndListParam.class);
resourceMetaParams.put(Constants.PARAM_PROFILE, UriParam.class);
resourceMetaAndParams.put(Constants.PARAM_PROFILE, UriAndListParam.class);
resourceMetaParams.put(Constants.PARAM_SECURITY, TokenParam.class);
resourceMetaAndParams.put(Constants.PARAM_SECURITY, TokenAndListParam.class);
RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
HashSet<String> excludeElementsInEncoded = new HashSet<String>();
excludeElementsInEncoded.add("id");
excludeElementsInEncoded.add("*.meta");
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
}
@PersistenceContext(type = PersistenceContextType.TRANSACTION) @PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager; protected EntityManager myEntityManager;
@Autowired @Autowired
protected IdHelperService myIdHelperService;
@Autowired
protected IForcedIdDao myForcedIdDao; protected IForcedIdDao myForcedIdDao;
@Autowired @Autowired
protected ISearchResultDao mySearchResultDao; protected ISearchResultDao mySearchResultDao;
@ -255,6 +134,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
protected IResourceIndexedSearchParamStringDao myResourceIndexedSearchParamStringDao; protected IResourceIndexedSearchParamStringDao myResourceIndexedSearchParamStringDao;
@Autowired() @Autowired()
protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao; protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao;
@Autowired
protected IResourceLinkDao myResourceLinkDao;
@Autowired() @Autowired()
protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao; protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
@Autowired() @Autowired()
@ -279,6 +160,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
protected IResourceTagDao myResourceTagDao; protected IResourceTagDao myResourceTagDao;
@Autowired @Autowired
protected IResourceSearchViewDao myResourceViewDao; protected IResourceSearchViewDao myResourceViewDao;
@Autowired
protected ISearchParamRegistry mySearchParamRegistry;
@Autowired(required = true) @Autowired(required = true)
private DaoConfig myConfig; private DaoConfig myConfig;
private FhirContext myContext; private FhirContext myContext;
@ -290,20 +173,20 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
private ISearchParamExtractor mySearchParamExtractor; private ISearchParamExtractor mySearchParamExtractor;
@Autowired @Autowired
private ISearchParamPresenceSvc mySearchParamPresenceSvc; private ISearchParamPresenceSvc mySearchParamPresenceSvc;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
//@Autowired //@Autowired
//private ISearchResultDao mySearchResultDao; //private ISearchResultDao mySearchResultDao;
@Autowired @Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao; private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
private ApplicationContext myApplicationContext; @Autowired
private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> myResourceTypeToDao; private BeanFactory beanFactory;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private MatchUrlService myMatchUrlService;
@Autowired
private SearchParamExtractorService mySearchParamExtractorService;
public static void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) { private ApplicationContext myApplicationContext;
if (theRequestDetails != null) {
theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST);
}
}
/** /**
* Returns the newly created forced ID. If the entity already had a forced ID, or if * Returns the newly created forced ID. If the entity already had a forced ID, or if
@ -311,7 +194,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
*/ */
protected ForcedId createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId, boolean theCreateForPureNumericIds) { protected ForcedId createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId, boolean theCreateForPureNumericIds) {
if (theId.isEmpty() == false && theId.hasIdPart() && theEntity.getForcedId() == null) { if (theId.isEmpty() == false && theId.hasIdPart() && theEntity.getForcedId() == null) {
if (!theCreateForPureNumericIds && isValidPid(theId)) { if (!theCreateForPureNumericIds && IdHelperService.isValidPid(theId)) {
return null; return null;
} }
@ -328,6 +211,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
protected ExpungeOutcome doExpunge(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions) { protected ExpungeOutcome doExpunge(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions) {
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
ourLog.info("Expunge: ResourceName[{}] Id[{}] Version[{}] Options[{}]", theResourceName, theResourceId, theVersion, theExpungeOptions); ourLog.info("Expunge: ResourceName[{}] Id[{}] Version[{}] Options[{}]", theResourceName, theResourceId, theVersion, theExpungeOptions);
if (!getConfig().isExpungeEnabled()) { if (!getConfig().isExpungeEnabled()) {
@ -367,11 +251,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
}); });
/* /*
* Delete any search result cache entries pointing to the given resource * Delete any search result cache entries pointing to the given resource. We do
* this in batches to avoid sending giant batches of parameters to the DB
*/ */
if (resourceIds.getContent().size() > 0) { List<List<Long>> partitions = Lists.partition(resourceIds.getContent(), 800);
for (List<Long> nextPartition : partitions) {
ourLog.info("Expunging any search results pointing to {} resources", nextPartition.size());
txTemplate.execute(t -> { txTemplate.execute(t -> {
mySearchResultDao.deleteByResourceIds(resourceIds.getContent()); mySearchResultDao.deleteByResourceIds(nextPartition);
return null; return null;
}); });
} }
@ -438,7 +325,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
ourLog.info("** BEGINNING GLOBAL $expunge **"); ourLog.info("** BEGINNING GLOBAL $expunge **");
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(t -> { txTemplate.execute(t -> {
doExpungeEverythingQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null"); doExpungeEverythingQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null");
doExpungeEverythingQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null"); doExpungeEverythingQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null");
@ -521,6 +408,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
myResourceIndexedSearchParamQuantityDao.deleteAll(resource.getParamsQuantity()); myResourceIndexedSearchParamQuantityDao.deleteAll(resource.getParamsQuantity());
myResourceIndexedSearchParamStringDao.deleteAll(resource.getParamsString()); myResourceIndexedSearchParamStringDao.deleteAll(resource.getParamsString());
myResourceIndexedSearchParamTokenDao.deleteAll(resource.getParamsToken()); myResourceIndexedSearchParamTokenDao.deleteAll(resource.getParamsToken());
myResourceLinkDao.deleteAll(resource.getResourceLinks());
myResourceLinkDao.deleteAll(resource.getResourceLinksAsTarget());
myResourceTagDao.deleteAll(resource.getTags()); myResourceTagDao.deleteAll(resource.getTags());
resource.getTags().clear(); resource.getTags().clear();
@ -529,7 +418,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
ForcedId forcedId = resource.getForcedId(); ForcedId forcedId = resource.getForcedId();
resource.setForcedId(null); resource.setForcedId(null);
myResourceTableDao.saveAndFlush(resource); myResourceTableDao.saveAndFlush(resource);
myForcedIdDao.delete(forcedId); myIdHelperService.delete(forcedId);
} }
myResourceTableDao.delete(resource); myResourceTableDao.delete(resource);
@ -560,7 +449,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
} }
} }
private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set<ResourceTag> allDefs) { private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set<ResourceTag> allDefs) {
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource); TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tagList != null) { if (tagList != null) {
@ -647,7 +535,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
if (theResourceName != null) { if (theResourceName != null) {
Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName); Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName);
if (theResourceId != null) { if (theResourceId != null) {
cq.where(typePredicate, builder.equal(from.get("myResourceId"), translateForcedIdToPid(theResourceName, theResourceId.getIdPart()))); cq.where(typePredicate, builder.equal(from.get("myResourceId"), myIdHelperService.translateForcedIdToPid(theResourceName, theResourceId.getIdPart())));
} else { } else {
cq.where(typePredicate); cq.where(typePredicate);
} }
@ -659,6 +547,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
} }
} }
} }
protected void flushJpaSession() { protected void flushJpaSession() {
SessionImpl session = (SessionImpl) myEntityManager.unwrap(Session.class); SessionImpl session = (SessionImpl) myEntityManager.unwrap(Session.class);
int insertionCount = session.getActionQueue().numberOfInsertions(); int insertionCount = session.getActionQueue().numberOfInsertions();
@ -679,8 +568,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return retVal; return retVal;
} }
@Override protected DaoConfig getConfig() {
public DaoConfig getConfig() {
return myConfig; return myConfig;
} }
@ -710,51 +598,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
} }
} }
@Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) { public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = getDaos(); return myDaoRegistry.getResourceDaoIfExists(theType);
IFhirResourceDao<R> dao = (IFhirResourceDao<R>) resourceTypeToDao.get(theType);
return dao;
} }
private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getDaos() { protected IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
if (myResourceTypeToDao == null) { return myDaoRegistry.getDaoOrThrowException(theClass);
Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = new HashMap<>();
Map<String, IFhirResourceDao> daos = myApplicationContext.getBeansOfType(IFhirResourceDao.class, false, false);
String[] beanNames = myApplicationContext.getBeanNamesForType(IFhirResourceDao.class);
for (IFhirResourceDao<?> next : daos.values()) {
resourceTypeToDao.put(next.getResourceType(), next);
}
if (this instanceof IFhirResourceDao<?>) {
IFhirResourceDao<?> thiz = (IFhirResourceDao<?>) this;
resourceTypeToDao.put(thiz.getResourceType(), thiz);
}
myResourceTypeToDao = resourceTypeToDao;
}
return Collections.unmodifiableMap(myResourceTypeToDao);
}
@Override
public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao() {
return myResourceIndexedCompositeStringUniqueDao;
}
@Override
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
Map<String, RuntimeSearchParam> params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName());
return params.get(theParamName);
}
@Override
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
return mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()).values();
} }
protected TagDefinition getTagOrNull(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { protected TagDefinition getTagOrNull(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
@ -892,39 +742,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
theProvider.setSearchCoordinatorSvc(mySearchCoordinatorSvc); theProvider.setSearchCoordinatorSvc(mySearchCoordinatorSvc);
} }
@Override
public boolean isLogicalReference(IIdType theId) { public boolean isLogicalReference(IIdType theId) {
Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical(); return LogicalReferenceHelper.isLogicalReference(myConfig, theId);
if (treatReferencesAsLogical != null) {
for (String nextLogicalRef : treatReferencesAsLogical) {
nextLogicalRef = trim(nextLogicalRef);
if (nextLogicalRef.charAt(nextLogicalRef.length() - 1) == '*') {
if (theId.getValue().startsWith(nextLogicalRef.substring(0, nextLogicalRef.length() - 1))) {
return true;
}
} else {
if (theId.getValue().equals(nextLogicalRef)) {
return true;
}
}
}
}
return false;
}
public static void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE);
}
} }
@Override @Override
public SearchBuilder newSearchBuilder() { public SearchBuilder newSearchBuilder() {
SearchBuilder builder = new SearchBuilder( return beanFactory.getBean(SearchBuilder.class, this);
getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao,
myForcedIdDao, myTerminologySvc, mySerarchParamRegistry, myResourceTagDao, myResourceViewDao);
return builder;
} }
public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) { public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
@ -944,34 +768,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
} }
} }
public String parseContentTextIntoWords(IBaseResource theResource) {
StringBuilder retVal = new StringBuilder();
@SuppressWarnings("rawtypes")
List<IPrimitiveType> childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class);
for (@SuppressWarnings("rawtypes")
IPrimitiveType nextType : childElements) {
if (nextType instanceof StringDt || nextType.getClass().getSimpleName().equals("StringType")) {
String nextValue = nextType.getValueAsString();
if (isNotBlank(nextValue)) {
retVal.append(nextValue.replace("\n", " ").replace("\r", " "));
retVal.append("\n");
}
}
}
return retVal.toString();
}
@Override
public void populateFullTextFields(final IBaseResource theResource, ResourceTable theEntity) {
if (theEntity.getDeleted() != null) {
theEntity.setNarrativeTextParsedIntoWords(null);
theEntity.setContentTextParsedIntoWords(null);
} else {
theEntity.setNarrativeTextParsedIntoWords(parseNarrativeTextIntoWords(theResource));
theEntity.setContentTextParsedIntoWords(parseContentTextIntoWords(theResource));
}
}
private void populateResourceIdFromEntity(IBaseResourceEntity theEntity, final IBaseResource theResource) { private void populateResourceIdFromEntity(IBaseResourceEntity theEntity, final IBaseResource theResource) {
IIdType id = theEntity.getIdDt(); IIdType id = theEntity.getIdDt();
if (getContext().getVersion().getVersion().isRi()) { if (getContext().getVersion().getVersion().isRi()) {
@ -1006,7 +802,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
if (theEntity.getDeleted() == null) { if (theEntity.getDeleted() == null) {
encoding = myConfig.getResourceEncoding(); encoding = myConfig.getResourceEncoding();
Set<String> excludeElements = EXCLUDE_ELEMENTS_IN_ENCODED; Set<String> excludeElements = ResourceMetaParams.EXCLUDE_ELEMENTS_IN_ENCODED;
theEntity.setFhirVersion(myContext.getVersion().getVersion()); theEntity.setFhirVersion(myContext.getVersion().getVersion());
bytes = encodeResource(theResource, encoding, excludeElements, myContext); bytes = encodeResource(theResource, encoding, excludeElements, myContext);
@ -1247,25 +1043,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
// nothing // nothing
} }
@Override
public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) {
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
SearchParameterMap paramMap = translateMatchUrl(this, myContext, theMatchUrl, resourceDef);
paramMap.setLoadSynchronous(true);
if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
}
IFhirResourceDao<R> dao = getDao(theResourceType);
if (dao == null) {
throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName());
}
return dao.searchForIds(paramMap);
}
@CoverageIgnore @CoverageIgnore
public BaseHasResource readEntity(IIdType theValueId) { public BaseHasResource readEntity(IIdType theValueId) {
throw new NotImplementedException(""); throw new NotImplementedException("");
@ -1283,7 +1060,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
} }
} }
/** /**
* 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. * 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.
* <p> * <p>
@ -1334,11 +1110,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return false; return false;
} }
@PostConstruct
public void startClearCaches() {
myResourceTypeToDao = null;
}
private ExpungeOutcome toExpungeOutcome(ExpungeOptions theExpungeOptions, AtomicInteger theRemainingCount) { private ExpungeOutcome toExpungeOutcome(ExpungeOptions theExpungeOptions, AtomicInteger theRemainingCount) {
return new ExpungeOutcome() return new ExpungeOutcome()
.setDeletedCount(theExpungeOptions.getLimit() - theRemainingCount.get()); .setDeletedCount(theExpungeOptions.getLimit() - theRemainingCount.get());
@ -1452,7 +1223,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return retVal; return retVal;
} }
@Override
public String toResourceName(Class<? extends IBaseResource> theResourceType) { public String toResourceName(Class<? extends IBaseResource> theResourceType) {
return myContext.getResourceDefinition(theResourceType).getName(); return myContext.getResourceDefinition(theResourceType).getName();
} }
@ -1466,16 +1236,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return new SliceImpl<>(Collections.singletonList(theVersion.getId())); return new SliceImpl<>(Collections.singletonList(theVersion.getId()));
} }
@Override
public Long translateForcedIdToPid(String theResourceName, String theResourceId) {
return translateForcedIdToPids(getConfig(), new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0);
}
protected List<Long> translateForcedIdToPids(IIdType theId) {
return translateForcedIdToPids(getConfig(), theId, myForcedIdDao);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, ResourceTable protected ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, ResourceTable
theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
@ -1507,14 +1267,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
theEntity.setPublished(theUpdateTime); theEntity.setPublished(theUpdateTime);
} }
ResourceIndexedSearchParams existingParams = new ResourceIndexedSearchParams(this, theEntity); ResourceIndexedSearchParams existingParams = new ResourceIndexedSearchParams(theEntity);
ResourceIndexedSearchParams newParams = null; ResourceIndexedSearchParams newParams = null;
EncodedResource changed; EncodedResource changed;
if (theDeletedTimestampOrNull != null) { if (theDeletedTimestampOrNull != null) {
newParams = new ResourceIndexedSearchParams(this); newParams = new ResourceIndexedSearchParams();
theEntity.setDeleted(theDeletedTimestampOrNull); theEntity.setDeleted(theDeletedTimestampOrNull);
theEntity.setUpdated(theDeletedTimestampOrNull); theEntity.setUpdated(theDeletedTimestampOrNull);
@ -1530,7 +1290,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
if (thePerformIndexing) { if (thePerformIndexing) {
newParams = new ResourceIndexedSearchParams(this, theUpdateTime, theEntity, theResource, existingParams); newParams = new ResourceIndexedSearchParams();
mySearchParamExtractorService.populateFromResource(newParams, this, theUpdateTime, theEntity, theResource, existingParams);
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true); changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
@ -1541,9 +1302,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
theEntity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue()); theEntity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue());
} }
newParams.setParams(theEntity); newParams.setParamsOn(theEntity);
theEntity.setIndexStatus(INDEX_STATUS_INDEXED); theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
populateFullTextFields(theResource, theEntity); populateFullTextFields(myContext, theResource, theEntity);
} else { } else {
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, false); changed = populateResourceIntoEntity(theRequest, theResource, theEntity, false);
@ -1616,7 +1377,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
*/ */
if (thePerformIndexing) { if (thePerformIndexing) {
Map<String, Boolean> presentSearchParams = new HashMap<>(); Map<String, Boolean> presentSearchParams = new HashMap<>();
// TODO KHS null check?
for (String nextKey : newParams.getPopulatedResourceLinkParameters()) { for (String nextKey : newParams.getPopulatedResourceLinkParameters()) {
presentSearchParams.put(nextKey, Boolean.TRUE); presentSearchParams.put(nextKey, Boolean.TRUE);
} }
@ -1635,8 +1395,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
* Indexing * Indexing
*/ */
if (thePerformIndexing) { if (thePerformIndexing) {
newParams.removeCommon(theEntity, existingParams); mySearchParamExtractorService.removeCommon(newParams, theEntity, existingParams);
} // if thePerformIndexing } // if thePerformIndexing
if (theResource != null) { if (theResource != null) {
@ -1836,6 +1595,50 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
} }
@Override
public ISearchParamRegistry getSearchParamRegistry() {
return mySearchParamRegistry;
}
public static void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST);
}
}
public static void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE);
}
}
public static String parseContentTextIntoWords(FhirContext theContext, IBaseResource theResource) {
StringBuilder retVal = new StringBuilder();
@SuppressWarnings("rawtypes")
List<IPrimitiveType> childElements = theContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class);
for (@SuppressWarnings("rawtypes")
IPrimitiveType nextType : childElements) {
if (nextType instanceof StringDt || nextType.getClass().getSimpleName().equals("StringType")) {
String nextValue = nextType.getValueAsString();
if (isNotBlank(nextValue)) {
retVal.append(nextValue.replace("\n", " ").replace("\r", " "));
retVal.append("\n");
}
}
}
return retVal.toString();
}
public static void populateFullTextFields(final FhirContext theContext, final IBaseResource theResource, ResourceTable theEntity) {
if (theEntity.getDeleted() != null) {
theEntity.setNarrativeTextParsedIntoWords(null);
theEntity.setContentTextParsedIntoWords(null);
} else {
theEntity.setNarrativeTextParsedIntoWords(parseNarrativeTextIntoWords(theResource));
theEntity.setContentTextParsedIntoWords(parseContentTextIntoWords(theContext, theResource));
}
}
public static String decodeResource(byte[] theResourceBytes, ResourceEncodingEnum theResourceEncoding) { public static String decodeResource(byte[] theResourceBytes, ResourceEncodingEnum theResourceEncoding) {
String resourceText = null; String resourceText = null;
switch (theResourceEncoding) { switch (theResourceEncoding) {
@ -1875,44 +1678,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return bytes; return bytes;
} }
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<? extends IQueryParameterAnd<?>> 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<? extends IQueryParameterType> 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) { public static String normalizeString(String theString) {
CharArrayWriter outBuffer = new CharArrayWriter(theString.length()); CharArrayWriter outBuffer = new CharArrayWriter(theString.length());
@ -1995,169 +1760,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return retVal; return retVal;
} }
protected static Long translateForcedIdToPid(DaoConfig theDaoConfig, String theResourceName, String theResourceId, IForcedIdDao
theForcedIdDao) {
return translateForcedIdToPids(theDaoConfig, new IdDt(theResourceName, theResourceId), theForcedIdDao).get(0);
}
static List<Long> translateForcedIdToPids(DaoConfig theDaoConfig, IIdType theId, IForcedIdDao theForcedIdDao) {
Validate.isTrue(theId.hasIdPart());
if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(theId)) {
return Collections.singletonList(theId.getIdPartAsLong());
} else {
List<ForcedId> forcedId;
if (theId.hasResourceType()) {
forcedId = theForcedIdDao.findByTypeAndForcedId(theId.getResourceType(), theId.getIdPart());
} else {
forcedId = theForcedIdDao.findByForcedId(theId.getIdPart());
}
if (forcedId.isEmpty() == false) {
List<Long> retVal = new ArrayList<>(forcedId.size());
for (ForcedId next : forcedId) {
retVal.add(next.getResourcePid());
}
return retVal;
} else {
throw new ResourceNotFoundException(theId);
}
}
}
public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String
theMatchUrl, RuntimeResourceDefinition resourceDef) {
SearchParameterMap paramMap = new SearchParameterMap();
List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
ArrayListMultimap<String, QualifiedParamList> 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<QualifiedParamList> 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(theContext, nextParamName, paramList);
paramMap.setLastUpdated(p1);
}
}
continue;
}
if (Constants.PARAM_HAS.equals(nextParamName)) {
IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(theContext, RestSearchParameterTypeEnum.HAS, nextParamName, paramList);
paramMap.add(nextParamName, param);
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(theContext, nextParamName, (paramList));
paramMap.add(nextParamName, type);
} else if (nextParamName.startsWith("_")) {
// ignore these since they aren't search params (e.g. _sort)
} else {
RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, 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 = ParameterUtil.parseQueryParams(theContext, paramDef, nextParamName, paramList);
paramMap.add(nextParamName, param);
}
}
return paramMap;
}
public static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
List<NameValuePair> 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;
}
public static void validateResourceType(BaseHasResource theEntity, String theResourceName) { public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
if (!theResourceName.equals(theEntity.getResourceType())) { if (!theResourceName.equals(theEntity.getResourceType())) {
throw new ResourceNotFoundException( throw new ResourceNotFoundException(
"Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType()); "Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType());
} }
} }
@Override
public ISearchParamExtractor getSearchParamExtractor() {
return mySearchParamExtractor;
}
@Override
public ISearchParamRegistry getSearchParamRegistry() {
return mySearchParamRegistry;
}
@Override
public EntityManager getEntityManager() {
return myEntityManager;
}
@Override
public Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getResourceTypeToDao() {
return myResourceTypeToDao;
}
@Override
public IForcedIdDao getForcedIdDao() {
return myForcedIdDao;
}
} }

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
@ -75,8 +74,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
protected PlatformTransactionManager myPlatformTransactionManager; protected PlatformTransactionManager myPlatformTransactionManager;
@Autowired(required = false) @Autowired(required = false)
protected IFulltextSearchSvc mySearchDao; protected IFulltextSearchSvc mySearchDao;
@Autowired()
protected ISearchResultDao mySearchResultDao;
@Autowired @Autowired
protected DaoConfig myDaoConfig; protected DaoConfig myDaoConfig;
@Autowired @Autowired
@ -86,6 +83,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private String mySecondaryPrimaryKeyParamName; private String mySecondaryPrimaryKeyParamName;
@Autowired @Autowired
private ISearchParamRegistry mySearchParamRegistry; private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private MatchUrlService myMatchUrlService;
@Override @Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
@ -272,7 +271,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public DeleteMethodOutcome deleteByUrl(String theUrl, List<DeleteConflict> deleteConflicts, RequestDetails theRequest) { public DeleteMethodOutcome deleteByUrl(String theUrl, List<DeleteConflict> deleteConflicts, RequestDetails theRequest) {
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
Set<Long> resource = processMatchUrl(theUrl, myResourceType); Set<Long> resource = myMatchUrlService.processMatchUrl(theUrl, myResourceType);
if (resource.size() > 1) { if (resource.size() > 1) {
if (myDaoConfig.isAllowMultipleDelete() == false) { if (myDaoConfig.isAllowMultipleDelete() == false) {
throw new PreconditionFailedException(getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size())); throw new PreconditionFailedException(getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size()));
@ -372,7 +371,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity.setResourceType(toResourceName(theResource)); entity.setResourceType(toResourceName(theResource));
if (isNotBlank(theIfNoneExist)) { if (isNotBlank(theIfNoneExist)) {
Set<Long> match = processMatchUrl(theIfNoneExist, myResourceType); Set<Long> match = myMatchUrlService.processMatchUrl(theIfNoneExist, myResourceType);
if (match.size() > 1) { if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size()); String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
throw new PreconditionFailedException(msg); throw new PreconditionFailedException(msg);
@ -793,7 +792,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
myResourceName = def.getName(); myResourceName = def.getName();
if (mySecondaryPrimaryKeyParamName != null) { if (mySecondaryPrimaryKeyParamName != null) {
RuntimeSearchParam sp = getSearchParamByName(def, mySecondaryPrimaryKeyParamName); RuntimeSearchParam sp = mySearchParamRegistry.getSearchParamByName(def, mySecondaryPrimaryKeyParamName);
if (sp == null) { if (sp == null) {
throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]");
} }
@ -849,7 +848,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override @Override
public Set<Long> processMatchUrl(String theMatchUrl) { public Set<Long> processMatchUrl(String theMatchUrl) {
return processMatchUrl(theMatchUrl, getResourceType()); return myMatchUrlService.processMatchUrl(theMatchUrl, getResourceType());
} }
@Override @Override
@ -911,7 +910,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId) { public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId) {
validateResourceTypeAndThrowIllegalArgumentException(theId); validateResourceTypeAndThrowIllegalArgumentException(theId);
Long pid = translateForcedIdToPid(getResourceName(), theId.getIdPart()); Long pid = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart());
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid);
if (entity == null) { if (entity == null) {
@ -951,7 +950,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
protected ResourceTable readEntityLatestVersion(IIdType theId) { protected ResourceTable readEntityLatestVersion(IIdType theId) {
ResourceTable entity = myEntityManager.find(ResourceTable.class, translateForcedIdToPid(getResourceName(), theId.getIdPart())); ResourceTable entity = myEntityManager.find(ResourceTable.class, myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart()));
if (entity == null) { if (entity == null) {
throw new ResourceNotFoundException(theId); throw new ResourceNotFoundException(theId);
} }
@ -1192,7 +1191,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Should not be null since the check above would have caught it // Should not be null since the check above would have caught it
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceName); RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceName);
RuntimeSearchParam paramDef = getSearchParamByName(resourceDef, qualifiedParamName.getParamName()); RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
for (String nextValue : theSource.get(nextParamName)) { for (String nextValue : theSource.get(nextParamName)) {
if (isNotBlank(nextValue)) { if (isNotBlank(nextValue)) {
@ -1232,7 +1231,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IIdType resourceId; IIdType resourceId;
if (isNotBlank(theMatchUrl)) { if (isNotBlank(theMatchUrl)) {
StopWatch sw = new StopWatch(); StopWatch sw = new StopWatch();
Set<Long> match = processMatchUrl(theMatchUrl, myResourceType); Set<Long> match = myMatchUrlService.processMatchUrl(theMatchUrl, myResourceType);
if (match.size() > 1) { if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size()); String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size());
throw new PreconditionFailedException(msg); throw new PreconditionFailedException(msg);

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.jpa.util.ExpungeOutcome;
@ -51,8 +50,6 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
@Autowired @Autowired
@Qualifier("myResourceCountsCache") @Qualifier("myResourceCountsCache")
public ResourceCountCache myResourceCountsCache; public ResourceCountCache myResourceCountsCache;
@Autowired
private IForcedIdDao myForcedIdDao;
private ReentrantLock myReindexLock = new ReentrantLock(false); private ReentrantLock myReindexLock = new ReentrantLock(false);
@Autowired @Autowired
private ITermConceptDao myTermConceptDao; private ITermConceptDao myTermConceptDao;
@ -60,12 +57,9 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
private ISearchParamRegistry mySearchParamRegistry; private ISearchParamRegistry mySearchParamRegistry;
@Autowired @Autowired
private PlatformTransactionManager myTxManager; private PlatformTransactionManager myTxManager;
@Autowired
private IResourceTableDao myResourceTableDao;
@Override @Override
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.NEVER)
public ExpungeOutcome expunge(ExpungeOptions theExpungeOptions) { public ExpungeOutcome expunge(ExpungeOptions theExpungeOptions) {
return doExpunge(null, null, null, theExpungeOptions); return doExpunge(null, null, null, theExpungeOptions);
} }

View File

@ -348,5 +348,16 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
protected abstract RuntimeSearchParam toRuntimeSp(SP theNextSp); protected abstract RuntimeSearchParam toRuntimeSp(SP theNextSp);
@Override
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
Map<String, RuntimeSearchParam> params = getActiveSearchParams(theResourceDef.getName());
return params.get(theParamName);
}
@Override
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
return getActiveSearchParams(theResourceDef.getName()).values();
}
} }

View File

@ -156,6 +156,7 @@ public class DaoConfig {
private List<Integer> mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1); private List<Integer> mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1);
private List<WarmCacheEntry> myWarmCacheEntries = new ArrayList<>(); private List<WarmCacheEntry> myWarmCacheEntries = new ArrayList<>();
private boolean myDisableHashBasedSearches; private boolean myDisableHashBasedSearches;
private boolean myEnableInMemorySubscriptionMatching = true;
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC; private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
/** /**
@ -1448,6 +1449,50 @@ public class DaoConfig {
myDisableHashBasedSearches = theDisableHashBasedSearches; myDisableHashBasedSearches = theDisableHashBasedSearches;
} }
/**
* If set to <code>false</code> (default is true) the server will not use
* in-memory subscription searching and instead use the database matcher for all subscription
* criteria matching.
* <p>
* When there are subscriptions registered
* on the server, the default behaviour is to compare the changed resource to the
* subscription criteria directly in-memory without going out to the database.
* Certain types of subscription criteria, e.g. chained references of queries with
* qualifiers or prefixes, are not supported by the in-memory matcher and will fall back
* to a database matcher.
* <p>
* The database matcher performs a query against the
* database by prepending ?id=XYZ to the subscription criteria where XYZ is the id of the changed entity
*
* @since 3.6.1
*/
public boolean isEnableInMemorySubscriptionMatching() {
return myEnableInMemorySubscriptionMatching;
}
/**
* If set to <code>false</code> (default is true) the server will not use
* in-memory subscription searching and instead use the database matcher for all subscription
* criteria matching.
* <p>
* When there are subscriptions registered
* on the server, the default behaviour is to compare the changed resource to the
* subscription criteria directly in-memory without going out to the database.
* Certain types of subscription criteria, e.g. chained references of queries with
* qualifiers or prefixes, are not supported by the in-memory matcher and will fall back
* to a database matcher.
* <p>
* The database matcher performs a query against the
* database by prepending ?id=XYZ to the subscription criteria where XYZ is the id of the changed entity
*
* @since 3.6.1
*/
public void setEnableInMemorySubscriptionMatching(boolean theEnableInMemorySubscriptionMatching) {
myEnableInMemorySubscriptionMatching = theEnableInMemorySubscriptionMatching;
}
public enum IndexEnabledEnum { public enum IndexEnabledEnum {
ENABLED, ENABLED,
DISABLED DISABLED

View File

@ -23,22 +23,27 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Component("myDaoRegistry")
public class DaoRegistry implements ApplicationContextAware { public class DaoRegistry implements ApplicationContextAware {
private ApplicationContext myAppCtx; private ApplicationContext myAppCtx;
@Autowired @Autowired
private FhirContext myCtx; private FhirContext myContext;
private volatile Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao; private volatile Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao;
private volatile IFhirSystemDao<?, ?> mySystemDao; private volatile IFhirSystemDao<?, ?> mySystemDao;
@ -47,8 +52,8 @@ public class DaoRegistry implements ApplicationContextAware {
myAppCtx = theApplicationContext; myAppCtx = theApplicationContext;
} }
public IFhirSystemDao<?, ?> getSystemDao() { public IFhirSystemDao getSystemDao() {
IFhirSystemDao<?, ?> retVal = mySystemDao; IFhirSystemDao retVal = mySystemDao;
if (retVal == null) { if (retVal == null) {
retVal = myAppCtx.getBean(IFhirSystemDao.class); retVal = myAppCtx.getBean(IFhirSystemDao.class);
mySystemDao = retVal; mySystemDao = retVal;
@ -56,10 +61,11 @@ public class DaoRegistry implements ApplicationContextAware {
return retVal; return retVal;
} }
public IFhirResourceDao<?> getResourceDao(String theResourceName) { public IFhirResourceDao getResourceDao(String theResourceName) {
IFhirResourceDao<?> retVal = getResourceNameToResourceDao().get(theResourceName); init();
IFhirResourceDao retVal = myResourceNameToResourceDao.get(theResourceName);
if (retVal == null) { if (retVal == null) {
List<String> supportedResourceTypes = getResourceNameToResourceDao() List<String> supportedResourceTypes = myResourceNameToResourceDao
.keySet() .keySet()
.stream() .stream()
.sorted() .sorted()
@ -67,26 +73,54 @@ public class DaoRegistry implements ApplicationContextAware {
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + theResourceName + " - Can handle: " + supportedResourceTypes); throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + theResourceName + " - Can handle: " + supportedResourceTypes);
} }
return retVal; return retVal;
} }
public <T extends IBaseResource> IFhirResourceDao<T> getResourceDao(Class<T> theResourceType) { public <R extends IBaseResource> IFhirResourceDao<R> getResourceDao(Class<R> theResourceType) {
String resourceName = myCtx.getResourceDefinition(theResourceType).getName(); IFhirResourceDao<R> retVal = getResourceDaoIfExists(theResourceType);
Validate.notNull(retVal, "No DAO exists for resource type %s - Have: %s", theResourceType, myResourceNameToResourceDao);
return retVal;
}
public <T extends IBaseResource> IFhirResourceDao<T> getResourceDaoIfExists(Class<T> theResourceType) {
String resourceName = myContext.getResourceDefinition(theResourceType).getName();
return (IFhirResourceDao<T>) getResourceDao(resourceName); return (IFhirResourceDao<T>) getResourceDao(resourceName);
} }
private Map<String, IFhirResourceDao<?>> getResourceNameToResourceDao() { private void init() {
Map<String, IFhirResourceDao<?>> retVal = myResourceNameToResourceDao; if (myResourceNameToResourceDao != null && !myResourceNameToResourceDao.isEmpty()) {
if (retVal == null || retVal.isEmpty()) { return;
retVal = new HashMap<>(); }
Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
for (IFhirResourceDao nextResourceDao : resourceDaos.values()) { Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
RuntimeResourceDefinition nextResourceDef = myCtx.getResourceDefinition(nextResourceDao.getResourceType());
retVal.put(nextResourceDef.getName(), nextResourceDao); initializeMaps(resourceDaos.values());
} }
myResourceNameToResourceDao = retVal;
private void initializeMaps(Collection<IFhirResourceDao> theResourceDaos) {
myResourceNameToResourceDao = new HashMap<>();
for (IFhirResourceDao nextResourceDao : theResourceDaos) {
RuntimeResourceDefinition nextResourceDef = myContext.getResourceDefinition(nextResourceDao.getResourceType());
myResourceNameToResourceDao.put(nextResourceDef.getName(), nextResourceDao);
}
}
public IFhirResourceDao getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
IFhirResourceDao retVal = getResourceDao(theClass);
if (retVal == null) {
List<String> supportedResourceNames = myResourceNameToResourceDao
.keySet()
.stream()
.map(t -> myContext.getResourceDefinition(t).getName())
.sorted()
.collect(Collectors.toList());
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + myContext.getResourceDefinition(theClass).getName() + " - Can handle: " + supportedResourceNames);
} }
return retVal; return retVal;
} }
public void setResourceDaos(Collection<IFhirResourceDao> theResourceDaos) {
initializeMaps(theResourceDaos);
}
} }

View File

@ -68,7 +68,6 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
@ -82,6 +81,8 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
@Autowired @Autowired
private PlatformTransactionManager myTxManager; private PlatformTransactionManager myTxManager;
@Autowired @Autowired
private MatchUrlService myMatchUrlService;
@Autowired
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) { private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) {
@ -243,7 +244,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
requestDetails.setParameters(new HashMap<String, String[]>()); requestDetails.setParameters(new HashMap<String, String[]>());
if (qIndex != -1) { if (qIndex != -1) {
String params = url.substring(qIndex); String params = url.substring(qIndex);
List<NameValuePair> parameters = translateMatchUrl(params); List<NameValuePair> parameters = myMatchUrlService.translateMatchUrl(params);
for (NameValuePair next : parameters) { for (NameValuePair next : parameters) {
paramValues.put(next.getName(), next.getValue()); paramValues.put(next.getName(), next.getValue());
} }

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
@ -41,7 +42,6 @@ import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction; import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder; import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.dstu3.model.BaseResource; import org.hl7.fhir.dstu3.model.BaseResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -65,11 +65,14 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
@Autowired @Autowired
protected IForcedIdDao myForcedIdDao; protected IForcedIdDao myForcedIdDao;
private Boolean ourDisabled;
@Autowired @Autowired
private DaoConfig myDaoConfig; private DaoConfig myDaoConfig;
@Autowired
private IdHelperService myIdHelperService;
private Boolean ourDisabled;
/** /**
* Constructor * Constructor
*/ */
@ -225,7 +228,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
StringParam idParm = (StringParam) idParam; StringParam idParm = (StringParam) idParam;
idParamValue = idParm.getValue(); idParamValue = idParm.getValue();
} }
pid = BaseHapiFhirDao.translateForcedIdToPid(myDaoConfig, theResourceName, idParamValue, myForcedIdDao); pid = myIdHelperService.translateForcedIdToPid(theResourceName, idParamValue);
} }
Long referencingPid = pid; Long referencingPid = pid;
@ -278,7 +281,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) { if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
throw new InvalidRequestException("Invalid context: " + theContext); throw new InvalidRequestException("Invalid context: " + theContext);
} }
Long pid = BaseHapiFhirDao.translateForcedIdToPid( myDaoConfig, contextParts[0], contextParts[1], myForcedIdDao); Long pid = myIdHelperService.translateForcedIdToPid(contextParts[0], contextParts[1]);
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);

View File

@ -1,17 +1,13 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.IBaseResourceEntity; import ca.uhn.fhir.jpa.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.ResourceTag; import ca.uhn.fhir.jpa.entity.ResourceTag;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
/* /*
* #%L * #%L
@ -41,10 +37,6 @@ public interface IDao {
FhirContext getContext(); FhirContext getContext();
RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName);
Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef);
/** /**
* Populate all of the runtime dependencies that a bundle provider requires in order to work * Populate all of the runtime dependencies that a bundle provider requires in order to work
*/ */
@ -52,12 +44,9 @@ public interface IDao {
ISearchBuilder newSearchBuilder(); ISearchBuilder newSearchBuilder();
void populateFullTextFields(IBaseResource theResource, ResourceTable theEntity);
<R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType);
IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation); IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation);
<R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation); <R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation);
ISearchParamRegistry getSearchParamRegistry();
} }

View File

@ -42,7 +42,7 @@ public interface ISearchBuilder {
void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager theEntityManager, void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager theEntityManager,
FhirContext theContext, IDao theDao); FhirContext theContext, IDao theDao);
Set<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode, Set<Long> loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
DateRangeParam theLastUpdated, String theSearchIdOrDescription); DateRangeParam theLastUpdated, String theSearchIdOrDescription);
/** /**

View File

@ -20,9 +20,11 @@ package ca.uhn.fhir.jpa.dao;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -53,4 +55,8 @@ public interface ISearchParamRegistry {
* Request that the cache be refreshed at the next convenient time (in a different thread) * Request that the cache be refreshed at the next convenient time (in a different thread)
*/ */
void requestRefresh(); void requestRefresh();
RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName);
Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef);
} }

View File

@ -0,0 +1,52 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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.hl7.fhir.instance.model.api.IIdType;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.trim;
public class LogicalReferenceHelper {
public static boolean isLogicalReference(DaoConfig myConfig, IIdType theId) {
Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical();
if (treatReferencesAsLogical != null) {
for (String nextLogicalRef : treatReferencesAsLogical) {
nextLogicalRef = trim(nextLogicalRef);
if (nextLogicalRef.charAt(nextLogicalRef.length() - 1) == '*') {
if (theId.getValue().startsWith(nextLogicalRef.substring(0, nextLogicalRef.length() - 1))) {
return true;
}
} else {
if (theId.getValue().equals(nextLogicalRef)) {
return true;
}
}
}
}
return false;
}
}

View File

@ -0,0 +1,206 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.CoverageIgnore;
import com.google.common.collect.ArrayListMultimap;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Service
public class MatchUrlService {
@Autowired
private FhirContext myContext;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private MatchUrlService myMatchUrlService;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) {
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceType);
SearchParameterMap paramMap = myMatchUrlService.translateMatchUrl(theMatchUrl, resourceDef);
paramMap.setLoadSynchronous(true);
if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
}
IFhirResourceDao<R> dao = myDaoRegistry.getResourceDao(theResourceType);
if (dao == null) {
throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName());
}
return dao.searchForIds(paramMap);
}
public SearchParameterMap translateMatchUrl(String
theMatchUrl, RuntimeResourceDefinition resourceDef) {
SearchParameterMap paramMap = new SearchParameterMap();
List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
ArrayListMultimap<String, QualifiedParamList> 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<QualifiedParamList> 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(myContext, nextParamName, paramList);
paramMap.setLastUpdated(p1);
}
}
continue;
}
if (Constants.PARAM_HAS.equals(nextParamName)) {
IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(myContext, RestSearchParameterTypeEnum.HAS, nextParamName, paramList);
paramMap.add(nextParamName, param);
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 (ResourceMetaParams.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(myContext, nextParamName, (paramList));
paramMap.add(nextParamName, type);
} else if (nextParamName.startsWith("_")) {
// ignore these since they aren't search params (e.g. _sort)
} else {
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, 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 = ParameterUtil.parseQueryParams(myContext, paramDef, nextParamName, paramList);
paramMap.add(nextParamName, param);
}
}
return paramMap;
}
public List<NameValuePair> translateMatchUrl(String theMatchUrl) {
List<NameValuePair> 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;
}
@CoverageIgnore
protected IQueryParameterAnd newInstanceAnd(String chain) {
IQueryParameterAnd type;
Class<? extends IQueryParameterAnd> clazz = ResourceMetaParams.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
public IQueryParameterType newInstanceType(String chain) {
IQueryParameterType type;
Class<? extends IQueryParameterType> clazz = ResourceMetaParams.RESOURCE_META_PARAMS.get(chain);
try {
type = clazz.newInstance();
} catch (Exception e) {
throw new InternalErrorException("Failure creating instance of " + clazz, e);
}
return type;
}
}

View File

@ -0,0 +1,63 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.r4.model.BaseResource;
import java.util.*;
public class ResourceMetaParams {
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/
public static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/
public static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
public static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
static {
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>();
resourceMetaParams.put(BaseResource.SP_RES_ID, StringParam.class);
resourceMetaAndParams.put(BaseResource.SP_RES_ID, StringAndListParam.class);
resourceMetaParams.put(BaseResource.SP_RES_LANGUAGE, StringParam.class);
resourceMetaAndParams.put(BaseResource.SP_RES_LANGUAGE, StringAndListParam.class);
resourceMetaParams.put(Constants.PARAM_TAG, TokenParam.class);
resourceMetaAndParams.put(Constants.PARAM_TAG, TokenAndListParam.class);
resourceMetaParams.put(Constants.PARAM_PROFILE, UriParam.class);
resourceMetaAndParams.put(Constants.PARAM_PROFILE, UriAndListParam.class);
resourceMetaParams.put(Constants.PARAM_SECURITY, TokenParam.class);
resourceMetaAndParams.put(Constants.PARAM_SECURITY, TokenAndListParam.class);
RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
HashSet<String> excludeElementsInEncoded = new HashSet<String>();
excludeElementsInEncoded.add("id");
excludeElementsInEncoded.add("*.meta");
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
}
}

View File

@ -21,10 +21,11 @@ package ca.uhn.fhir.jpa.dao;
*/ */
import ca.uhn.fhir.context.*; import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao; import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
@ -69,10 +70,15 @@ import org.hibernate.query.criteria.internal.predicate.BooleanStaticAssertionPre
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.persistence.criteria.*; import javax.persistence.criteria.*;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -87,6 +93,8 @@ import static org.apache.commons.lang3.StringUtils.*;
* searches for resources * searches for resources
*/ */
@SuppressWarnings("JpaQlInspection") @SuppressWarnings("JpaQlInspection")
@Component
@Scope("prototype")
public class SearchBuilder implements ISearchBuilder { public class SearchBuilder implements ISearchBuilder {
private static final List<Long> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>()); private static final List<Long> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>());
@ -97,26 +105,42 @@ public class SearchBuilder implements ISearchBuilder {
private static String ourLastHandlerThreadForUnitTest; private static String ourLastHandlerThreadForUnitTest;
private static boolean ourTrackHandlersForUnitTest; private static boolean ourTrackHandlersForUnitTest;
private final boolean myDontUseHashesForSearch; private final boolean myDontUseHashesForSearch;
private final DaoConfig myDaoConfig;
@Autowired
protected IResourceTagDao myResourceTagDao; protected IResourceTagDao myResourceTagDao;
@Autowired
private IResourceSearchViewDao myResourceSearchViewDao; private IResourceSearchViewDao myResourceSearchViewDao;
@Autowired
private FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
private IdHelperService myIdHelperService;
@Autowired(required = false)
private IFulltextSearchSvc myFulltextSearchSvc;
@Autowired
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private IHapiTerminologySvc myTerminologySvc;
@Autowired
private MatchUrlService myMatchUrlService;
@Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
private List<Long> myAlsoIncludePids; private List<Long> myAlsoIncludePids;
private CriteriaBuilder myBuilder; private CriteriaBuilder myBuilder;
private BaseHapiFhirDao<?> myCallingDao; private BaseHapiFhirDao<?> myCallingDao;
private FhirContext myContext;
private EntityManager myEntityManager;
private IForcedIdDao myForcedIdDao;
private IFulltextSearchSvc myFulltextSearchSvc;
private Map<JoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap(); private Map<JoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap();
private SearchParameterMap myParams; private SearchParameterMap myParams;
private ArrayList<Predicate> myPredicates; private ArrayList<Predicate> myPredicates;
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
private String myResourceName; private String myResourceName;
private AbstractQuery<Long> myResourceTableQuery; private AbstractQuery<Long> myResourceTableQuery;
private Root<ResourceTable> myResourceTableRoot; private Root<ResourceTable> myResourceTableRoot;
private Class<? extends IBaseResource> myResourceType; private Class<? extends IBaseResource> myResourceType;
private ISearchParamRegistry mySearchParamRegistry;
private String mySearchUuid; private String mySearchUuid;
private IHapiTerminologySvc myTerminologySvc;
private int myFetchSize; private int myFetchSize;
private Integer myMaxResultsToFetch; private Integer myMaxResultsToFetch;
private Set<Long> myPidSet; private Set<Long> myPidSet;
@ -124,22 +148,10 @@ public class SearchBuilder implements ISearchBuilder {
/** /**
* Constructor * Constructor
*/ */
SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, SearchBuilder(BaseHapiFhirDao<?> theDao) {
IFulltextSearchSvc theFulltextSearchSvc, BaseHapiFhirDao<?> theDao,
IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao,
IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry,
IResourceTagDao theResourceTagDao, IResourceSearchViewDao theResourceViewDao) {
myContext = theFhirContext;
myEntityManager = theEntityManager;
myFulltextSearchSvc = theFulltextSearchSvc;
myCallingDao = theDao; myCallingDao = theDao;
myDontUseHashesForSearch = theDao.getConfig().getDisableHashBasedSearches(); myDaoConfig = theDao.getConfig();
myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao; myDontUseHashesForSearch = myDaoConfig.getDisableHashBasedSearches();
myForcedIdDao = theForcedIdDao;
myTerminologySvc = theTerminologySvc;
mySearchParamRegistry = theSearchParamRegistry;
myResourceTagDao = theResourceTagDao;
myResourceSearchViewDao = theResourceViewDao;
} }
@Override @Override
@ -220,24 +232,24 @@ public class SearchBuilder implements ISearchBuilder {
assert parameterName != null; assert parameterName != null;
String paramName = parameterName.replaceAll("\\..*", ""); String paramName = parameterName.replaceAll("\\..*", "");
RuntimeSearchParam owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, paramName); RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
if (owningParameterDef == null) { if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName); throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
} }
owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, owningParameter); owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, owningParameter);
if (owningParameterDef == null) { if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + owningParameter); throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + owningParameter);
} }
Class<? extends IBaseResource> resourceType = targetResourceDefinition.getImplementingClass(); Class<? extends IBaseResource> resourceType = targetResourceDefinition.getImplementingClass();
Set<Long> match = myCallingDao.processMatchUrl(matchUrl, resourceType); Set<Long> match = myMatchUrlService.processMatchUrl(matchUrl, resourceType);
if (match.isEmpty()) { if (match.isEmpty()) {
// Pick a PID that can never match // Pick a PID that can never match
match = Collections.singleton(-1L); match = Collections.singleton(-1L);
} }
Join<ResourceTable, ResourceLink> join = myResourceTableRoot.join("myIncomingResourceLinks", JoinType.LEFT); Join<ResourceTable, ResourceLink> join = myResourceTableRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
Predicate predicate = join.get("mySourceResourcePid").in(match); Predicate predicate = join.get("mySourceResourcePid").in(match);
myPredicates.add(predicate); myPredicates.add(predicate);
@ -373,7 +385,7 @@ public class SearchBuilder implements ISearchBuilder {
IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null); IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null);
if (dt.hasBaseUrl()) { if (dt.hasBaseUrl()) {
if (myCallingDao.getConfig().getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) { if (myDaoConfig.getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) {
dt = dt.toUnqualified(); dt = dt.toUnqualified();
} else { } else {
ourLog.debug("Searching for resource link with target URL: {}", dt.getValue()); ourLog.debug("Searching for resource link with target URL: {}", dt.getValue());
@ -385,7 +397,7 @@ public class SearchBuilder implements ISearchBuilder {
List<Long> targetPid; List<Long> targetPid;
try { try {
targetPid = myCallingDao.translateForcedIdToPids(dt); targetPid = myIdHelperService.translateForcedIdToPids(dt);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
// Use a PID that will never exist // Use a PID that will never exist
targetPid = Collections.singletonList(-1L); targetPid = Collections.singletonList(-1L);
@ -417,7 +429,7 @@ public class SearchBuilder implements ISearchBuilder {
if (resourceTypes.isEmpty()) { if (resourceTypes.isEmpty()) {
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceName); RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceName);
RuntimeSearchParam searchParamByName = myCallingDao.getSearchParamByName(resourceDef, theParamName); RuntimeSearchParam searchParamByName = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
if (searchParamByName == null) { if (searchParamByName == null) {
throw new InternalErrorException("Could not find parameter " + theParamName); throw new InternalErrorException("Could not find parameter " + theParamName);
} }
@ -491,10 +503,10 @@ public class SearchBuilder implements ISearchBuilder {
chain = chain.substring(0, qualifierIndex); chain = chain.substring(0, qualifierIndex);
} }
boolean isMeta = BaseHapiFhirDao.RESOURCE_META_PARAMS.containsKey(chain); boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain);
RuntimeSearchParam param = null; RuntimeSearchParam param = null;
if (!isMeta) { if (!isMeta) {
param = myCallingDao.getSearchParamByName(typeDef, chain); param = mySearchParamRegistry.getSearchParamByName(typeDef, chain);
if (param == null) { if (param == null) {
ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param); ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param);
continue; continue;
@ -512,7 +524,7 @@ public class SearchBuilder implements ISearchBuilder {
chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId); chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
((ReferenceParam) chainValue).setChain(remainingChain); ((ReferenceParam) chainValue).setChain(remainingChain);
} else if (isMeta) { } else if (isMeta) {
IQueryParameterType type = BaseHapiFhirDao.newInstanceType(chain); IQueryParameterType type = myMatchUrlService.newInstanceType(chain);
type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId); type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
chainValue = type; chainValue = type;
} else { } else {
@ -1162,7 +1174,6 @@ public class SearchBuilder implements ISearchBuilder {
private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder, private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom) { From<?, ResourceIndexedSearchParamString> theFrom) {
String rawSearchTerm; String rawSearchTerm;
DaoConfig daoConfig = myCallingDao.getConfig();
if (theParameter instanceof TokenParam) { if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter; TokenParam id = (TokenParam) theParameter;
if (!id.isText()) { if (!id.isText()) {
@ -1173,7 +1184,7 @@ public class SearchBuilder implements ISearchBuilder {
StringParam id = (StringParam) theParameter; StringParam id = (StringParam) theParameter;
rawSearchTerm = id.getValue(); rawSearchTerm = id.getValue();
if (id.isContains()) { if (id.isContains()) {
if (!daoConfig.isAllowContainsSearches()) { if (!myDaoConfig.isAllowContainsSearches()) {
throw new MethodNotAllowedException(":contains modifier is disabled on this server"); throw new MethodNotAllowedException(":contains modifier is disabled on this server");
} }
} }
@ -1191,7 +1202,7 @@ public class SearchBuilder implements ISearchBuilder {
if (myDontUseHashesForSearch) { if (myDontUseHashesForSearch) {
String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm); String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm);
if (myCallingDao.getConfig().isAllowContainsSearches()) { if (myDaoConfig.isAllowContainsSearches()) {
if (theParameter instanceof StringParam) { if (theParameter instanceof StringParam) {
if (((StringParam) theParameter).isContains()) { if (((StringParam) theParameter).isContains()) {
likeExpression = createLeftAndRightMatchLikeExpression(likeExpression); likeExpression = createLeftAndRightMatchLikeExpression(likeExpression);
@ -1230,13 +1241,13 @@ public class SearchBuilder implements ISearchBuilder {
String likeExpression; String likeExpression;
if (theParameter instanceof StringParam && if (theParameter instanceof StringParam &&
((StringParam) theParameter).isContains() && ((StringParam) theParameter).isContains() &&
daoConfig.isAllowContainsSearches()) { myDaoConfig.isAllowContainsSearches()) {
likeExpression = createLeftAndRightMatchLikeExpression(normalizedString); likeExpression = createLeftAndRightMatchLikeExpression(normalizedString);
} else { } else {
likeExpression = createLeftMatchLikeExpression(normalizedString); likeExpression = createLeftMatchLikeExpression(normalizedString);
} }
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(daoConfig, theResourceName, theParamName, normalizedString); Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig, theResourceName, theParamName, normalizedString);
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
return theBuilder.and(hashCode, singleCode); return theBuilder.and(hashCode, singleCode);
@ -1473,7 +1484,7 @@ public class SearchBuilder implements ISearchBuilder {
* of parameters passed in * of parameters passed in
*/ */
ourLog.debug("Checking for unique index for query: {}", theParams.toNormalizedQueryString(myContext)); ourLog.debug("Checking for unique index for query: {}", theParams.toNormalizedQueryString(myContext));
if (myCallingDao.getConfig().isUniqueIndexesEnabled()) { if (myDaoConfig.isUniqueIndexesEnabled()) {
if (myParams.getIncludes().isEmpty()) { if (myParams.getIncludes().isEmpty()) {
if (myParams.getRevIncludes().isEmpty()) { if (myParams.getRevIncludes().isEmpty()) {
if (myParams.getEverythingMode() == null) { if (myParams.getEverythingMode() == null) {
@ -1600,7 +1611,7 @@ public class SearchBuilder implements ISearchBuilder {
if (myParams.get(IAnyResource.SP_RES_ID) != null) { if (myParams.get(IAnyResource.SP_RES_ID) != null) {
StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0); StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
Long pid = BaseHapiFhirDao.translateForcedIdToPid(myCallingDao.getConfig(), myResourceName, idParm.getValue(), myForcedIdDao); Long pid = myIdHelperService.translateForcedIdToPid(myResourceName, idParm.getValue());
if (myAlsoIncludePids == null) { if (myAlsoIncludePids == null) {
myAlsoIncludePids = new ArrayList<>(1); myAlsoIncludePids = new ArrayList<>(1);
} }
@ -1677,7 +1688,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
private Predicate createResourceLinkPathPredicate(String theResourceName, String theParamName, From<?, ? extends ResourceLink> from) { private Predicate createResourceLinkPathPredicate(String theResourceName, String theParamName, From<?, ? extends ResourceLink> from) {
return createResourceLinkPathPredicate(myCallingDao, myContext, theParamName, from, theResourceName); return createResourceLinkPathPredicate(myContext, theParamName, from, theResourceName);
} }
/** /**
@ -1713,7 +1724,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName); RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
RuntimeSearchParam param = myCallingDao.getSearchParamByName(resourceDef, theSort.getParamName()); RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theSort.getParamName());
if (param == null) { if (param == null) {
throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'"); throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'");
} }
@ -1807,7 +1818,7 @@ public class SearchBuilder implements ISearchBuilder {
String retVal = theSystem; String retVal = theSystem;
if (retVal == null) { if (retVal == null) {
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName); RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
RuntimeSearchParam param = myCallingDao.getSearchParamByName(resourceDef, theParamName); RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
if (param != null) { if (param != null) {
Set<String> valueSetUris = Sets.newHashSet(); Set<String> valueSetUris = Sets.newHashSet();
for (String nextPath : param.getPathsSplit()) { for (String nextPath : param.getPathsSplit()) {
@ -1957,7 +1968,7 @@ public class SearchBuilder implements ISearchBuilder {
* so it can't be Collections.emptySet() or some such thing * so it can't be Collections.emptySet() or some such thing
*/ */
@Override @Override
public HashSet<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, public HashSet<Long> loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
boolean theReverseMode, DateRangeParam theLastUpdated, String theSearchIdOrDescription) { boolean theReverseMode, DateRangeParam theLastUpdated, String theSearchIdOrDescription) {
if (theMatches.size() == 0) { if (theMatches.size() == 0) {
return new HashSet<>(); return new HashSet<>();
@ -2017,7 +2028,7 @@ public class SearchBuilder implements ISearchBuilder {
String paramName = nextInclude.getParamName(); String paramName = nextInclude.getParamName();
if (isNotBlank(paramName)) { if (isNotBlank(paramName)) {
param = theCallingDao.getSearchParamByName(def, paramName); param = mySearchParamRegistry.getSearchParamByName(def, paramName);
} else { } else {
param = null; param = null;
} }
@ -2088,6 +2099,7 @@ public class SearchBuilder implements ISearchBuilder {
private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams) { private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams) {
myParams = theParams; myParams = theParams;
theParams.clean();
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : myParams.entrySet()) { for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : myParams.entrySet()) {
String nextParamName = nextParamEntry.getKey(); String nextParamName = nextParamEntry.getKey();
List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue(); List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue();
@ -2099,37 +2111,6 @@ public class SearchBuilder implements ISearchBuilder {
private void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) { private void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) {
/*
* Filter out
*/
for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) {
List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx);
for (int orListIdx = 0; orListIdx < nextOrList.size(); orListIdx++) {
IQueryParameterType nextOr = nextOrList.get(orListIdx);
boolean hasNoValue = false;
if (nextOr.getMissing() != null) {
continue;
}
if (nextOr instanceof QuantityParam) {
if (isBlank(((QuantityParam) nextOr).getValueAsString())) {
hasNoValue = true;
}
}
if (hasNoValue) {
ourLog.debug("Ignoring empty parameter: {}", theParamName);
nextOrList.remove(orListIdx);
orListIdx--;
}
}
if (nextOrList.isEmpty()) {
theAndOrParams.remove(andListIdx);
andListIdx--;
}
}
if (theAndOrParams.isEmpty()) { if (theAndOrParams.isEmpty()) {
return; return;
} }
@ -2288,7 +2269,7 @@ public class SearchBuilder implements ISearchBuilder {
private int myCurrentOffset; private int myCurrentOffset;
private ArrayList<Long> myCurrentPids; private ArrayList<Long> myCurrentPids;
private Long myNext; private Long myNext;
private int myPageSize = myCallingDao.getConfig().getEverythingIncludesFetchPageSize(); private int myPageSize = myDaoConfig.getEverythingIncludesFetchPageSize();
IncludesIterator(Set<Long> thePidSet) { IncludesIterator(Set<Long> thePidSet) {
myCurrentPids = new ArrayList<>(thePidSet); myCurrentPids = new ArrayList<>(thePidSet);
@ -2316,7 +2297,7 @@ public class SearchBuilder implements ISearchBuilder {
myCurrentOffset = end; myCurrentOffset = end;
Collection<Long> pidsToScan = myCurrentPids.subList(start, end); Collection<Long> pidsToScan = myCurrentPids.subList(start, end);
Set<Include> includes = Collections.singleton(new Include("*", true)); Set<Include> includes = Collections.singleton(new Include("*", true));
Set<Long> newPids = loadIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated(), mySearchUuid); Set<Long> newPids = loadIncludes(myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated(), mySearchUuid);
myCurrentIterator = newPids.iterator(); myCurrentIterator = newPids.iterator();
} }
@ -2368,7 +2349,7 @@ public class SearchBuilder implements ISearchBuilder {
// If we don't have a query yet, create one // If we don't have a query yet, create one
if (myResultsIterator == null) { if (myResultsIterator == null) {
if (myMaxResultsToFetch == null) { if (myMaxResultsToFetch == null) {
myMaxResultsToFetch = myCallingDao.getConfig().getFetchSizeDefaultMaximum(); myMaxResultsToFetch = myDaoConfig.getFetchSizeDefaultMaximum();
} }
final TypedQuery<Long> query = createQuery(mySort, myMaxResultsToFetch, false); final TypedQuery<Long> query = createQuery(mySort, myMaxResultsToFetch, false);
@ -2483,7 +2464,7 @@ public class SearchBuilder implements ISearchBuilder {
if (myWrap == null) { if (myWrap == null) {
ourLog.debug("Searching for unique index matches over {} candidate query strings", myUniqueQueryStrings.size()); ourLog.debug("Searching for unique index matches over {} candidate query strings", myUniqueQueryStrings.size());
StopWatch sw = new StopWatch(); StopWatch sw = new StopWatch();
Collection<Long> resourcePids = myCallingDao.getResourceIndexedCompositeStringUniqueDao().findResourcePidsByQueryStrings(myUniqueQueryStrings); Collection<Long> resourcePids = myResourceIndexedCompositeStringUniqueDao.findResourcePidsByQueryStrings(myUniqueQueryStrings);
ourLog.debug("Found {} unique index matches in {}ms", resourcePids.size(), sw.getMillis()); ourLog.debug("Found {} unique index matches in {}ms", resourcePids.size(), sw.getMillis());
myWrap = resourcePids.iterator(); myWrap = resourcePids.iterator();
} }
@ -2621,10 +2602,10 @@ public class SearchBuilder implements ISearchBuilder {
return likeExpression.replace("%", "[%]") + "%"; return likeExpression.replace("%", "[%]") + "%";
} }
private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, From<?, ? extends ResourceLink> theFrom, private Predicate createResourceLinkPathPredicate(FhirContext theContext, String theParamName, From<?, ? extends ResourceLink> theFrom,
String theResourceType) { String theResourceType) {
RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType); RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType);
RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName); RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
List<String> path = param.getPathsSplit(); List<String> path = param.getPathsSplit();
/* /*

View File

@ -8,6 +8,7 @@ import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.util.ObjectUtil; import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -17,6 +18,7 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.*; import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
@ -40,6 +42,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/ */
public class SearchParameterMap extends LinkedHashMap<String, List<List<? extends IQueryParameterType>>> { public class SearchParameterMap extends LinkedHashMap<String, List<List<? extends IQueryParameterType>>> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterMap.class);
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -295,7 +298,7 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
* This method creates a URL query string representation of the parameters in this * This method creates a URL query string representation of the parameters in this
* object, excluding the part before the parameters, e.g. * object, excluding the part before the parameters, e.g.
* <p> * <p>
* <code>?name=smith&_sort=Patient:family</code> * <code>?name=smith&amp;_sort=Patient:family</code>
* </p> * </p>
* <p> * <p>
* This method <b>excludes</b> the <code>_count</code> parameter, * This method <b>excludes</b> the <code>_count</code> parameter,
@ -336,6 +339,10 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
IQueryParameterType firstValue = nextValuesAnd.get(0); IQueryParameterType firstValue = nextValuesAnd.get(0);
b.append(UrlUtil.escapeUrlParam(nextKey)); b.append(UrlUtil.escapeUrlParam(nextKey));
if (nextKey.equals(Constants.PARAM_HAS)) {
b.append(':');
}
if (firstValue.getMissing() != null) { if (firstValue.getMissing() != null) {
b.append(Constants.PARAMQUALIFIER_MISSING); b.append(Constants.PARAMQUALIFIER_MISSING);
b.append('='); b.append('=');
@ -433,6 +440,47 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return b.toString(); return b.toString();
} }
public void clean() {
for (Map.Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : this.entrySet()) {
String nextParamName = nextParamEntry.getKey();
List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue();
clean(nextParamName, andOrParams);
}
}
/*
* Filter out
*/
private void clean(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) {
for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) {
List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx);
for (int orListIdx = 0; orListIdx < nextOrList.size(); orListIdx++) {
IQueryParameterType nextOr = nextOrList.get(orListIdx);
boolean hasNoValue = false;
if (nextOr.getMissing() != null) {
continue;
}
if (nextOr instanceof QuantityParam) {
if (isBlank(((QuantityParam) nextOr).getValueAsString())) {
hasNoValue = true;
}
}
if (hasNoValue) {
ourLog.debug("Ignoring empty parameter: {}", theParamName);
nextOrList.remove(orListIdx);
orListIdx--;
}
}
if (nextOrList.isEmpty()) {
theAndOrParams.remove(andListIdx);
andListIdx--;
}
}
}
public enum EverythingModeEnum { public enum EverythingModeEnum {
/* /*
* Don't reorder! We rely on the ordinals * Don't reorder! We rely on the ordinals

View File

@ -43,7 +43,10 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding; import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding; import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.*; import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
@ -81,6 +84,8 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
@Autowired @Autowired
private ITransactionProcessorVersionAdapter<BUNDLE, BUNDLEENTRY> myVersionAdapter; private ITransactionProcessorVersionAdapter<BUNDLE, BUNDLEENTRY> myVersionAdapter;
@Autowired @Autowired
private MatchUrlService myMatchUrlService;
@Autowired
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BUNDLEENTRY nextEntry) { private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BUNDLEENTRY nextEntry) {
@ -390,7 +395,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
requestDetails.setParameters(new HashMap<>()); requestDetails.setParameters(new HashMap<>());
if (qIndex != -1) { if (qIndex != -1) {
String params = url.substring(qIndex); String params = url.substring(qIndex);
List<NameValuePair> parameters = BaseHapiFhirDao.translateMatchUrl(params); List<NameValuePair> parameters = myMatchUrlService.translateMatchUrl(params);
for (NameValuePair next : parameters) { for (NameValuePair next : parameters) {
paramValues.put(next.getName(), next.getValue()); paramValues.put(next.getName(), next.getValue());
} }
@ -481,284 +486,297 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
return myContext.getVersion().newIdType().setValue(theValue); return myContext.getVersion().newIdType().setValue(theValue);
} }
private Map<BUNDLEENTRY, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, String theActionName, Date theUpdateTime, Set<IIdType> theAllIds,
private Map<BUNDLEENTRY, ResourceTable> doTransactionWriteOperations(final ServletRequestDetails theRequestDetails, String theActionName, Date theUpdateTime, Set<IIdType> theAllIds,
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, BUNDLE theResponse, IdentityHashMap<BUNDLEENTRY, Integer> theOriginalRequestOrder, List<BUNDLEENTRY> theEntries, StopWatch theTransactionStopWatch) { Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, BUNDLE theResponse, IdentityHashMap<BUNDLEENTRY, Integer> theOriginalRequestOrder, List<BUNDLEENTRY> theEntries, StopWatch theTransactionStopWatch) {
Set<String> deletedResources = new HashSet<>();
List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<BUNDLEENTRY, ResourceTable> entriesToProcess = new IdentityHashMap<>();
Set<ResourceTable> nonUpdatedEntities = new HashSet<>();
Set<ResourceTable> updatedEntities = new HashSet<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
/* if (theRequestDetails != null) {
* Loop through the request and process any entries of type theRequestDetails.startDeferredOperationCallback();
* PUT, POST or DELETE }
*/ try {
for (int i = 0; i < theEntries.size(); i++) {
if (i % 100 == 0) { Set<String> deletedResources = new HashSet<>();
ourLog.debug("Processed {} non-GET entries out of {}", i, theEntries.size()); List<DeleteConflict> deleteConflicts = new ArrayList<>();
} Map<BUNDLEENTRY, ResourceTable> entriesToProcess = new IdentityHashMap<>();
Set<ResourceTable> nonUpdatedEntities = new HashSet<>();
Set<ResourceTable> updatedEntities = new HashSet<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
BUNDLEENTRY nextReqEntry = theEntries.get(i); /*
IBaseResource res = myVersionAdapter.getResource(nextReqEntry); * Loop through the request and process any entries of type
IIdType nextResourceId = null; * PUT, POST or DELETE
if (res != null) { */
for (int i = 0; i < theEntries.size(); i++) {
nextResourceId = res.getIdElement(); if (i % 100 == 0) {
ourLog.debug("Processed {} non-GET entries out of {}", i, theEntries.size());
if (!nextResourceId.hasIdPart()) {
if (isNotBlank(myVersionAdapter.getFullUrl(nextReqEntry))) {
nextResourceId = newIdType(myVersionAdapter.getFullUrl(nextReqEntry));
}
} }
if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+\\:.*") && !isPlaceholder(nextResourceId)) { BUNDLEENTRY nextReqEntry = theEntries.get(i);
throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'"); IBaseResource res = myVersionAdapter.getResource(nextReqEntry);
} IIdType nextResourceId = null;
if (res != null) {
if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) { nextResourceId = res.getIdElement();
nextResourceId = newIdType(toResourceName(res.getClass()), nextResourceId.getIdPart());
res.setId(nextResourceId);
}
/* if (!nextResourceId.hasIdPart()) {
* Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness if (isNotBlank(myVersionAdapter.getFullUrl(nextReqEntry))) {
*/ nextResourceId = newIdType(myVersionAdapter.getFullUrl(nextReqEntry));
if (isPlaceholder(nextResourceId)) { }
if (!theAllIds.add(nextResourceId)) { }
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId));
} if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+\\:.*") && !isPlaceholder(nextResourceId)) {
} else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'");
IIdType nextId = nextResourceId.toUnqualifiedVersionless(); }
if (!theAllIds.add(nextId)) {
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId)); if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) {
} nextResourceId = newIdType(toResourceName(res.getClass()), nextResourceId.getIdPart());
} res.setId(nextResourceId);
}
}
/*
String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry); * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness
String resourceType = res != null ? myContext.getResourceDefinition(res).getName() : null; */
Integer order = theOriginalRequestOrder.get(nextReqEntry); if (isPlaceholder(nextResourceId)) {
BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(theResponse).get(order); if (!theAllIds.add(nextResourceId)) {
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId));
theTransactionStopWatch.startTask("Bundle.entry[" + i + "]: " + verb + " " + defaultString(resourceType)); }
} else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) {
switch (verb) { IIdType nextId = nextResourceId.toUnqualifiedVersionless();
case "POST": { if (!theAllIds.add(nextId)) {
// CREATE throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId));
@SuppressWarnings("rawtypes")
IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
res.setId((String) null);
DaoMethodOutcome outcome;
String matchUrl = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
outcome = resourceDao.create(res, matchUrl, false, theRequestDetails);
if (nextResourceId != null) {
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
}
entriesToProcess.put(nextRespEntry, outcome.getEntity());
if (outcome.getCreated() == false) {
nonUpdatedEntities.add(outcome.getEntity());
} else {
if (isNotBlank(matchUrl)) {
conditionalRequestUrls.put(matchUrl, res.getClass());
} }
} }
break;
} }
case "DELETE": {
// DELETE String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry);
String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); String resourceType = res != null ? myContext.getResourceDefinition(res).getName() : null;
UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); Integer order = theOriginalRequestOrder.get(nextReqEntry);
ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> dao = toDao(parts, verb, url); BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(theResponse).get(order);
int status = Constants.STATUS_HTTP_204_NO_CONTENT;
if (parts.getResourceId() != null) { theTransactionStopWatch.startTask("Bundle.entry[" + i + "]: " + verb + " " + defaultString(resourceType));
IIdType deleteId = newIdType(parts.getResourceType(), parts.getResourceId());
if (!deletedResources.contains(deleteId.getValueAsString())) { switch (verb) {
DaoMethodOutcome outcome = dao.delete(deleteId, deleteConflicts, theRequestDetails); case "POST": {
if (outcome.getEntity() != null) { // CREATE
deletedResources.add(deleteId.getValueAsString()); @SuppressWarnings("rawtypes")
entriesToProcess.put(nextRespEntry, outcome.getEntity()); IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
res.setId((String) null);
DaoMethodOutcome outcome;
String matchUrl = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
outcome = resourceDao.create(res, matchUrl, false, theRequestDetails);
if (nextResourceId != null) {
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
}
entriesToProcess.put(nextRespEntry, outcome.getEntity());
if (outcome.getCreated() == false) {
nonUpdatedEntities.add(outcome.getEntity());
} else {
if (isNotBlank(matchUrl)) {
conditionalRequestUrls.put(matchUrl, res.getClass());
} }
} }
} else {
String matchUrl = parts.getResourceType() + '?' + parts.getParams();
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(matchUrl, deleteConflicts, theRequestDetails);
List<ResourceTable> allDeleted = deleteOutcome.getDeletedEntities();
for (ResourceTable deleted : allDeleted) {
deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless().getValueAsString());
}
if (allDeleted.isEmpty()) {
status = Constants.STATUS_HTTP_204_NO_CONTENT;
}
myVersionAdapter.setResponseOutcome(nextRespEntry, deleteOutcome.getOperationOutcome()); break;
} }
case "DELETE": {
myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(status)); // DELETE
String url = extractTransactionUrlOrThrowException(nextReqEntry, verb);
break; UrlUtil.UrlParts parts = UrlUtil.parseUrl(url);
} ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> dao = toDao(parts, verb, url);
case "PUT": { int status = Constants.STATUS_HTTP_204_NO_CONTENT;
// UPDATE if (parts.getResourceId() != null) {
@SuppressWarnings("rawtypes") IIdType deleteId = newIdType(parts.getResourceType(), parts.getResourceId());
IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); if (!deletedResources.contains(deleteId.getValueAsString())) {
DaoMethodOutcome outcome = dao.delete(deleteId, deleteConflicts, theRequestDetails);
String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); if (outcome.getEntity() != null) {
deletedResources.add(deleteId.getValueAsString());
DaoMethodOutcome outcome; entriesToProcess.put(nextRespEntry, outcome.getEntity());
UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); }
if (isNotBlank(parts.getResourceId())) { }
String version = null;
if (isNotBlank(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry))) {
version = ParameterUtil.parseETagValue(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry));
}
res.setId(newIdType(parts.getResourceType(), parts.getResourceId(), version));
outcome = resourceDao.update(res, null, false, theRequestDetails);
} else {
res.setId((String) null);
String matchUrl;
if (isNotBlank(parts.getParams())) {
matchUrl = parts.getResourceType() + '?' + parts.getParams();
} else { } else {
matchUrl = parts.getResourceType(); String matchUrl = parts.getResourceType() + '?' + parts.getParams();
} matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(matchUrl, deleteConflicts, theRequestDetails);
outcome = resourceDao.update(res, matchUrl, false, theRequestDetails); List<ResourceTable> allDeleted = deleteOutcome.getDeletedEntities();
if (Boolean.TRUE.equals(outcome.getCreated())) { for (ResourceTable deleted : allDeleted) {
conditionalRequestUrls.put(matchUrl, res.getClass()); deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless().getValueAsString());
} }
} if (allDeleted.isEmpty()) {
status = Constants.STATUS_HTTP_204_NO_CONTENT;
}
if (outcome.getCreated() == Boolean.FALSE) { myVersionAdapter.setResponseOutcome(nextRespEntry, deleteOutcome.getOperationOutcome());
updatedEntities.add(outcome.getEntity()); }
}
myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(status));
break;
}
case "PUT": {
// UPDATE
@SuppressWarnings("rawtypes")
IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
String url = extractTransactionUrlOrThrowException(nextReqEntry, verb);
DaoMethodOutcome outcome;
UrlUtil.UrlParts parts = UrlUtil.parseUrl(url);
if (isNotBlank(parts.getResourceId())) {
String version = null;
if (isNotBlank(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry))) {
version = ParameterUtil.parseETagValue(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry));
}
res.setId(newIdType(parts.getResourceType(), parts.getResourceId(), version));
outcome = resourceDao.update(res, null, false, theRequestDetails);
} else {
res.setId((String) null);
String matchUrl;
if (isNotBlank(parts.getParams())) {
matchUrl = parts.getResourceType() + '?' + parts.getParams();
} else {
matchUrl = parts.getResourceType();
}
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
outcome = resourceDao.update(res, matchUrl, false, theRequestDetails);
if (Boolean.TRUE.equals(outcome.getCreated())) {
conditionalRequestUrls.put(matchUrl, res.getClass());
}
}
if (outcome.getCreated() == Boolean.FALSE) {
updatedEntities.add(outcome.getEntity());
}
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
entriesToProcess.put(nextRespEntry, outcome.getEntity());
break;
}
case "GET":
default:
break;
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
entriesToProcess.put(nextRespEntry, outcome.getEntity());
break;
} }
case "GET":
default:
break;
theTransactionStopWatch.endCurrentTask();
}
/*
* Make sure that there are no conflicts from deletions. E.g. we can't delete something
* if something else has a reference to it.. Unless the thing that has a reference to it
* was also deleted as a part of this transaction, which is why we check this now at the
* end.
*/
deleteConflicts.removeIf(next ->
deletedResources.contains(next.getTargetId().toUnqualifiedVersionless().getValue()));
myDao.validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
/*
* Perform ID substitutions and then index each resource we have saved
*/
FhirTerser terser = myContext.newTerser();
theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources");
for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) {
IBaseResource nextResource = nextOutcome.getResource();
if (nextResource == null) {
continue;
}
// References
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource);
for (ResourceReferenceInfo nextRef : allRefs) {
IIdType nextId = nextRef.getResourceReference().getReferenceElement();
if (!nextId.hasIdPart()) {
continue;
}
if (theIdSubstitutions.containsKey(nextId)) {
IIdType newId = theIdSubstitutions.get(nextId);
ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId);
nextRef.getResourceReference().setReference(newId.getValue());
} else if (nextId.getValue().startsWith("urn:")) {
throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType());
} else {
ourLog.debug(" * Reference [{}] does not exist in bundle", nextId);
}
}
// URIs
Class<? extends IPrimitiveType<?>> uriType = (Class<? extends IPrimitiveType<?>>) myContext.getElementDefinition("uri").getImplementingClass();
List<? extends IPrimitiveType<?>> allUris = terser.getAllPopulatedChildElementsOfType(nextResource, uriType);
for (IPrimitiveType<?> nextRef : allUris) {
if (nextRef instanceof IIdType) {
continue; // No substitution on the resource ID itself!
}
IIdType nextUriString = newIdType(nextRef.getValueAsString());
if (theIdSubstitutions.containsKey(nextUriString)) {
IIdType newId = theIdSubstitutions.get(nextUriString);
ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId);
nextRef.setValueAsString(newId.getValue());
} else {
ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString);
}
}
IPrimitiveType<Date> deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
if (updatedEntities.contains(nextOutcome.getEntity())) {
myDao.updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
myDao.updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
}
} }
theTransactionStopWatch.endCurrentTask(); theTransactionStopWatch.endCurrentTask();
} theTransactionStopWatch.startTask("Flush writes to database");
flushJpaSession();
/* theTransactionStopWatch.endCurrentTask();
* Make sure that there are no conflicts from deletions. E.g. we can't delete something if (conditionalRequestUrls.size() > 0) {
* if something else has a reference to it.. Unless the thing that has a reference to it theTransactionStopWatch.startTask("Check for conflicts in conditional resources");
* was also deleted as a part of this transaction, which is why we check this now at the
* end.
*/
deleteConflicts.removeIf(next ->
deletedResources.contains(next.getTargetId().toUnqualifiedVersionless().getValue()));
myDao.validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
/*
* Perform ID substitutions and then index each resource we have saved
*/
FhirTerser terser = myContext.newTerser();
theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources");
for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) {
IBaseResource nextResource = nextOutcome.getResource();
if (nextResource == null) {
continue;
} }
// References /*
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource); * Double check we didn't allow any duplicates we shouldn't have
for (ResourceReferenceInfo nextRef : allRefs) { */
IIdType nextId = nextRef.getResourceReference().getReferenceElement(); for (Map.Entry<String, Class<? extends IBaseResource>> nextEntry : conditionalRequestUrls.entrySet()) {
if (!nextId.hasIdPart()) { String matchUrl = nextEntry.getKey();
Class<? extends IBaseResource> resType = nextEntry.getValue();
if (isNotBlank(matchUrl)) {
IFhirResourceDao<?> resourceDao = myDao.getDao(resType);
Set<Long> val = resourceDao.processMatchUrl(matchUrl);
if (val.size() > 1) {
throw new InvalidRequestException(
"Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?");
}
}
}
theTransactionStopWatch.endCurrentTask();
for (IIdType next : theAllIds) {
IIdType replacement = theIdSubstitutions.get(next);
if (replacement == null) {
continue; continue;
} }
if (theIdSubstitutions.containsKey(nextId)) { if (replacement.equals(next)) {
IIdType newId = theIdSubstitutions.get(nextId); continue;
ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId);
nextRef.getResourceReference().setReference(newId.getValue());
} else if (nextId.getValue().startsWith("urn:")) {
throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType());
} else {
ourLog.debug(" * Reference [{}] does not exist in bundle", nextId);
} }
ourLog.debug("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement);
} }
return entriesToProcess;
// URIs } finally {
Class<? extends IPrimitiveType<?>> uriType = (Class<? extends IPrimitiveType<?>>) myContext.getElementDefinition("uri").getImplementingClass(); if (theRequestDetails != null) {
List<? extends IPrimitiveType<?>> allUris = terser.getAllPopulatedChildElementsOfType(nextResource, uriType); theRequestDetails.stopDeferredRequestOperationCallbackAndRunDeferredItems();
for (IPrimitiveType<?> nextRef : allUris) {
if (nextRef instanceof IIdType) {
continue; // No substitution on the resource ID itself!
}
IIdType nextUriString = newIdType(nextRef.getValueAsString());
if (theIdSubstitutions.containsKey(nextUriString)) {
IIdType newId = theIdSubstitutions.get(nextUriString);
ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId);
nextRef.setValueAsString(newId.getValue());
} else {
ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString);
}
}
IPrimitiveType<Date> deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
if (updatedEntities.contains(nextOutcome.getEntity())) {
myDao.updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
myDao.updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
} }
} }
theTransactionStopWatch.endCurrentTask();
theTransactionStopWatch.startTask("Flush writes to database");
flushJpaSession();
theTransactionStopWatch.endCurrentTask();
if (conditionalRequestUrls.size() > 0) {
theTransactionStopWatch.startTask("Check for conflicts in conditional resources");
}
/*
* Double check we didn't allow any duplicates we shouldn't have
*/
for (Map.Entry<String, Class<? extends IBaseResource>> nextEntry : conditionalRequestUrls.entrySet()) {
String matchUrl = nextEntry.getKey();
Class<? extends IBaseResource> resType = nextEntry.getValue();
if (isNotBlank(matchUrl)) {
IFhirResourceDao<?> resourceDao = myDao.getDao(resType);
Set<Long> val = resourceDao.processMatchUrl(matchUrl);
if (val.size() > 1) {
throw new InvalidRequestException(
"Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?");
}
}
}
theTransactionStopWatch.endCurrentTask();
for (IIdType next : theAllIds) {
IIdType replacement = theIdSubstitutions.get(next);
if (replacement == null) {
continue;
}
if (replacement.equals(next)) {
continue;
}
ourLog.debug("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement);
}
return entriesToProcess;
} }
private IIdType newIdType(String theResourceType, String theResourceId, String theVersion) { private IIdType newIdType(String theResourceType, String theResourceId, String theVersion) {

View File

@ -25,5 +25,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.entity.ResourceLink; import ca.uhn.fhir.jpa.entity.ResourceLink;
public interface IResourceLinkDao extends JpaRepository<ResourceLink, Long> { public interface IResourceLinkDao extends JpaRepository<ResourceLink, Long> {
// nothing
} }

View File

@ -10,6 +10,7 @@ import org.springframework.data.repository.query.Param;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional;
/* /*
* #%L * #%L
@ -55,4 +56,12 @@ public interface IResourceReindexJobDao extends JpaRepository<ResourceReindexJob
@Modifying @Modifying
@Query("UPDATE ResourceReindexJobEntity j SET j.myThresholdLow = :low WHERE j.myId = :id") @Query("UPDATE ResourceReindexJobEntity j SET j.myThresholdLow = :low WHERE j.myId = :id")
void setThresholdLow(@Param("id") Long theId, @Param("low") Date theLow); void setThresholdLow(@Param("id") Long theId, @Param("low") Date theLow);
@Query("SELECT j.myReindexCount FROM ResourceReindexJobEntity j WHERE j.myId = :id")
Optional<Integer> getReindexCount(@Param("id") Long theId);
@Query("UPDATE ResourceReindexJobEntity j SET j.myReindexCount = :newCount WHERE j.myId = :id")
@Modifying
void setReindexCount(@Param("id") Long theId, @Param("newCount") int theNewCount);
} }

View File

@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
@ -65,9 +64,6 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3<CodeSys
@Autowired @Autowired
private ITermCodeSystemDao myCsDao; private ITermCodeSystemDao myCsDao;
@Autowired
private IHapiTerminologySvc myTerminologySvc;
@Autowired @Autowired
private ValidationSupportChain myValidationSupport; private ValidationSupportChain myValidationSupport;

View File

@ -0,0 +1,103 @@
package ca.uhn.fhir.jpa.dao.index;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Service
public class IdHelperService {
@Autowired
protected IForcedIdDao myForcedIdDao;
@Autowired(required = true)
private DaoConfig myDaoConfig;
public void delete(ForcedId forcedId) {
myForcedIdDao.delete(forcedId);
}
public Long translateForcedIdToPid(String theResourceName, String theResourceId) {
return translateForcedIdToPids(myDaoConfig, new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0);
}
public List<Long> translateForcedIdToPids(IIdType theId) {
return IdHelperService.translateForcedIdToPids(myDaoConfig, theId, myForcedIdDao);
}
static List<Long> translateForcedIdToPids(DaoConfig theDaoConfig, IIdType theId, IForcedIdDao theForcedIdDao) {
Validate.isTrue(theId.hasIdPart());
if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(theId)) {
return Collections.singletonList(theId.getIdPartAsLong());
} else {
List<ForcedId> forcedId;
if (theId.hasResourceType()) {
forcedId = theForcedIdDao.findByTypeAndForcedId(theId.getResourceType(), theId.getIdPart());
} else {
forcedId = theForcedIdDao.findByForcedId(theId.getIdPart());
}
if (!forcedId.isEmpty()) {
List<Long> retVal = new ArrayList<>(forcedId.size());
for (ForcedId next : forcedId) {
retVal.add(next.getResourcePid());
}
return retVal;
} else {
throw new ResourceNotFoundException(theId);
}
}
}
public String translatePidIdToForcedId(String theResourceType, Long theId) {
ForcedId forcedId = myForcedIdDao.findByResourcePid(theId);
if (forcedId != null) {
return forcedId.getResourceType() + '/' + forcedId.getForcedId();
} else {
return theResourceType + '/' + theId.toString();
}
}
public 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;
}
}

View File

@ -1,58 +0,0 @@
package ca.uhn.fhir.jpa.dao.index;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 java.util.Collection;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityManager;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.entity.ResourceTag;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.ISearchParamExtractor;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
public interface IndexingSupport {
public DaoConfig getConfig();
public ISearchParamExtractor getSearchParamExtractor();
public ISearchParamRegistry getSearchParamRegistry();
public FhirContext getContext();
public EntityManager getEntityManager();
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType);
public Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getResourceTypeToDao();
public boolean isLogicalReference(IIdType nextId);
public IForcedIdDao getForcedIdDao();
public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType);
public Long translateForcedIdToPid(String theResourceName, String theResourceId);
public String toResourceName(Class<? extends IBaseResource> theResourceType);
public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao();
}

View File

@ -20,334 +20,78 @@ package ca.uhn.fhir.jpa.dao.index;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.compare;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.persistence.EntityManager;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.Reference;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.PathAndRef;
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import java.util.*;
import ca.uhn.fhir.util.FhirTerser; import java.util.Map.Entry;
import ca.uhn.fhir.util.UrlUtil; import java.util.function.Predicate;
import static org.apache.commons.lang3.StringUtils.*;
public class ResourceIndexedSearchParams { public class ResourceIndexedSearchParams {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class);
// FIXME rename final Collection<ResourceIndexedSearchParamString> stringParams = new ArrayList<>();
private final IndexingSupport myIndexingService; final Collection<ResourceIndexedSearchParamToken> tokenParams = new HashSet<>();
final Collection<ResourceIndexedSearchParamNumber> numberParams = new ArrayList<>();
final Collection<ResourceIndexedSearchParamQuantity> quantityParams = new ArrayList<>();
final Collection<ResourceIndexedSearchParamDate> dateParams = new ArrayList<>();
final Collection<ResourceIndexedSearchParamUri> uriParams = new ArrayList<>();
final Collection<ResourceIndexedSearchParamCoords> coordsParams = new ArrayList<>();
private final Collection<ResourceIndexedSearchParamString> stringParams; final Collection<ResourceIndexedCompositeStringUnique> compositeStringUniques = new HashSet<>();
private final Collection<ResourceIndexedSearchParamToken> tokenParams; final Collection<ResourceLink> links = new HashSet<>();
private final Collection<ResourceIndexedSearchParamNumber> numberParams; final Set<String> populatedResourceLinkParameters = new HashSet<>();
private final Collection<ResourceIndexedSearchParamQuantity> quantityParams;
private final Collection<ResourceIndexedSearchParamDate> dateParams;
private final Collection<ResourceIndexedSearchParamUri> uriParams;
private final Collection<ResourceIndexedSearchParamCoords> coordsParams;
private final Collection<ResourceIndexedCompositeStringUnique> compositeStringUniques;
private final Collection<ResourceLink> links;
private Set<String> populatedResourceLinkParameters = Collections.emptySet();
public ResourceIndexedSearchParams(IndexingSupport indexingService, ResourceTable theEntity) { public ResourceIndexedSearchParams() {
this.myIndexingService = indexingService; }
stringParams = new ArrayList<>(); public ResourceIndexedSearchParams(ResourceTable theEntity) {
if (theEntity.isParamsStringPopulated()) { if (theEntity.isParamsStringPopulated()) {
stringParams.addAll(theEntity.getParamsString()); stringParams.addAll(theEntity.getParamsString());
} }
tokenParams = new ArrayList<>();
if (theEntity.isParamsTokenPopulated()) { if (theEntity.isParamsTokenPopulated()) {
tokenParams.addAll(theEntity.getParamsToken()); tokenParams.addAll(theEntity.getParamsToken());
} }
numberParams = new ArrayList<>();
if (theEntity.isParamsNumberPopulated()) { if (theEntity.isParamsNumberPopulated()) {
numberParams.addAll(theEntity.getParamsNumber()); numberParams.addAll(theEntity.getParamsNumber());
} }
quantityParams = new ArrayList<>();
if (theEntity.isParamsQuantityPopulated()) { if (theEntity.isParamsQuantityPopulated()) {
quantityParams.addAll(theEntity.getParamsQuantity()); quantityParams.addAll(theEntity.getParamsQuantity());
} }
dateParams = new ArrayList<>();
if (theEntity.isParamsDatePopulated()) { if (theEntity.isParamsDatePopulated()) {
dateParams.addAll(theEntity.getParamsDate()); dateParams.addAll(theEntity.getParamsDate());
} }
uriParams = new ArrayList<>();
if (theEntity.isParamsUriPopulated()) { if (theEntity.isParamsUriPopulated()) {
uriParams.addAll(theEntity.getParamsUri()); uriParams.addAll(theEntity.getParamsUri());
} }
coordsParams = new ArrayList<>();
if (theEntity.isParamsCoordsPopulated()) { if (theEntity.isParamsCoordsPopulated()) {
coordsParams.addAll(theEntity.getParamsCoords()); coordsParams.addAll(theEntity.getParamsCoords());
} }
links = new ArrayList<>();
if (theEntity.isHasLinks()) { if (theEntity.isHasLinks()) {
links.addAll(theEntity.getResourceLinks()); links.addAll(theEntity.getResourceLinks());
} }
compositeStringUniques = new ArrayList<>();
if (theEntity.isParamsCompositeStringUniquePresent()) { if (theEntity.isParamsCompositeStringUniquePresent()) {
compositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique()); compositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique());
} }
} }
public ResourceIndexedSearchParams(IndexingSupport indexingService) {
this.myIndexingService = indexingService;
stringParams = Collections.emptySet();
tokenParams = Collections.emptySet();
numberParams = Collections.emptySet();
quantityParams = Collections.emptySet();
dateParams = Collections.emptySet();
uriParams = Collections.emptySet();
coordsParams = Collections.emptySet();
links = Collections.emptySet();
compositeStringUniques = Collections.emptySet();
}
public ResourceIndexedSearchParams(IndexingSupport indexingService, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams existingParams) {
this.myIndexingService = indexingService;
stringParams = extractSearchParamStrings(theEntity, theResource);
numberParams = extractSearchParamNumber(theEntity, theResource);
quantityParams = extractSearchParamQuantity(theEntity, theResource);
dateParams = extractSearchParamDates(theEntity, theResource);
uriParams = extractSearchParamUri(theEntity, theResource);
coordsParams = extractSearchParamCoords(theEntity, theResource);
ourLog.trace("Storing date indexes: {}", dateParams);
tokenParams = new HashSet<>();
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) {
if (next instanceof ResourceIndexedSearchParamToken) {
tokenParams.add((ResourceIndexedSearchParamToken) next);
} else {
stringParams.add((ResourceIndexedSearchParamString) next);
}
}
Set<Entry<String, RuntimeSearchParam>> activeSearchParams = myIndexingService.getSearchParamRegistry().getActiveSearchParams(theEntity.getResourceType()).entrySet();
DaoConfig myConfig = indexingService.getConfig();
if (myConfig .getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.STRING, stringParams);
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams);
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams);
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.DATE, dateParams);
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.URI, uriParams);
findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams);
}
setUpdatedTime(stringParams, theUpdateTime);
setUpdatedTime(numberParams, theUpdateTime);
setUpdatedTime(quantityParams, theUpdateTime);
setUpdatedTime(dateParams, theUpdateTime);
setUpdatedTime(uriParams, theUpdateTime);
setUpdatedTime(coordsParams, theUpdateTime);
setUpdatedTime(tokenParams, theUpdateTime);
/*
* 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 = myIndexingService.getContext().newTerser();
List<IBaseReference> allRefs = terser.getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
for (IBaseReference nextRef : allRefs) {
IIdType nextId = nextRef.getReferenceElement();
String nextIdText = nextId.getValue();
if (nextIdText == null) {
continue;
}
int qmIndex = nextIdText.indexOf('?');
if (qmIndex != -1) {
for (int i = qmIndex - 1; i >= 0; i--) {
if (nextIdText.charAt(i) == '/') {
if (i < nextIdText.length() - 1 && nextIdText.charAt(i + 1) == '?') {
// Just in case the URL is in the form Patient/?foo=bar
continue;
}
nextIdText = nextIdText.substring(i + 1);
break;
}
}
String resourceTypeString = nextIdText.substring(0, nextIdText.indexOf('?')).replace("/", "");
RuntimeResourceDefinition matchResourceDef = myIndexingService.getContext().getResourceDefinition(resourceTypeString);
if (matchResourceDef == null) {
String msg = myIndexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlInvalidResourceType", nextId.getValue(), resourceTypeString);
throw new InvalidRequestException(msg);
}
Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
Set<Long> matches = myIndexingService.processMatchUrl(nextIdText, matchResourceType);
if (matches.isEmpty()) {
String msg = indexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
throw new ResourceNotFoundException(msg);
}
if (matches.size() > 1) {
String msg = indexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue());
throw new PreconditionFailedException(msg);
}
Long next = matches.iterator().next();
String newId = translatePidIdToForcedId(resourceTypeString, next);
ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
nextRef.setReference(newId);
}
}
}
links = new HashSet<>();
populatedResourceLinkParameters = extractResourceLinks(theEntity, theResource, links, theUpdateTime);
/*
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
*/
for (Iterator<ResourceLink> existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) {
ResourceLink nextExisting = existingLinkIter.next();
if (links.remove(nextExisting)) {
existingLinkIter.remove();
links.add(nextExisting);
}
}
/*
* Handle composites
*/
compositeStringUniques = extractCompositeStringUniques(theEntity, stringParams, tokenParams, numberParams, quantityParams, dateParams, uriParams, links);
}
public Collection<ResourceLink> getResourceLinks() { public Collection<ResourceLink> getResourceLinks() {
return links; return links;
} }
public void setParamsOn(ResourceTable theEntity) {
protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
return myIndexingService.getSearchParamExtractor().extractSearchParamCoords(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
return myIndexingService.getSearchParamExtractor().extractSearchParamDates(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
return myIndexingService.getSearchParamExtractor().extractSearchParamNumber(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
return myIndexingService.getSearchParamExtractor().extractSearchParamQuantity(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
return myIndexingService.getSearchParamExtractor().extractSearchParamStrings(theEntity, theResource);
}
protected Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
return myIndexingService.getSearchParamExtractor().extractSearchParamTokens(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
return myIndexingService.getSearchParamExtractor().extractSearchParamUri(theEntity, theResource);
}
@SuppressWarnings("unchecked")
private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type,
Collection<RT> paramCollection) {
for (Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
String nextParamName = nextEntry.getKey();
if (nextEntry.getValue().getParamType() == type) {
boolean haveParam = false;
for (BaseResourceIndexedSearchParam nextParam : paramCollection) {
if (nextParam.getParamName().equals(nextParamName)) {
haveParam = true;
break;
}
}
if (!haveParam) {
BaseResourceIndexedSearchParam param;
switch (type) {
case DATE:
param = new ResourceIndexedSearchParamDate();
break;
case NUMBER:
param = new ResourceIndexedSearchParamNumber();
break;
case QUANTITY:
param = new ResourceIndexedSearchParamQuantity();
break;
case STRING:
param = new ResourceIndexedSearchParamString()
.setDaoConfig(myIndexingService.getConfig());
break;
case TOKEN:
param = new ResourceIndexedSearchParamToken();
break;
case URI:
param = new ResourceIndexedSearchParamUri();
break;
case COMPOSITE:
case HAS:
case REFERENCE:
default:
continue;
}
param.setResource(theEntity);
param.setMissing(true);
param.setParamName(nextParamName);
paramCollection.add((RT) param);
}
}
}
}
public void setParams(ResourceTable theEntity) {
theEntity.setParamsString(stringParams); theEntity.setParamsString(stringParams);
theEntity.setParamsStringPopulated(stringParams.isEmpty() == false); theEntity.setParamsStringPopulated(stringParams.isEmpty() == false);
theEntity.setParamsToken(tokenParams); theEntity.setParamsToken(tokenParams);
@ -367,89 +111,23 @@ public class ResourceIndexedSearchParams {
theEntity.setHasLinks(links.isEmpty() == false); theEntity.setHasLinks(links.isEmpty() == false);
} }
public void setUpdatedTime(Date theUpdateTime) {
private Set<ResourceIndexedCompositeStringUnique> extractCompositeStringUniques(ResourceTable theEntity, Collection<ResourceIndexedSearchParamString> theStringParams, Collection<ResourceIndexedSearchParamToken> theTokenParams, Collection<ResourceIndexedSearchParamNumber> theNumberParams, Collection<ResourceIndexedSearchParamQuantity> theQuantityParams, Collection<ResourceIndexedSearchParamDate> theDateParams, Collection<ResourceIndexedSearchParamUri> theUriParams, Collection<ResourceLink> theLinks) { setUpdatedTime(stringParams, theUpdateTime);
Set<ResourceIndexedCompositeStringUnique> compositeStringUniques; setUpdatedTime(numberParams, theUpdateTime);
compositeStringUniques = new HashSet<>(); setUpdatedTime(quantityParams, theUpdateTime);
List<JpaRuntimeSearchParam> uniqueSearchParams = myIndexingService.getSearchParamRegistry().getActiveUniqueSearchParams(theEntity.getResourceType()); setUpdatedTime(dateParams, theUpdateTime);
for (JpaRuntimeSearchParam next : uniqueSearchParams) { setUpdatedTime(uriParams, theUpdateTime);
setUpdatedTime(coordsParams, theUpdateTime);
List<List<String>> partsChoices = new ArrayList<>(); setUpdatedTime(tokenParams, theUpdateTime);
for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
Collection<ResourceLink> linksForCompositePart = null;
Collection<String> linksForCompositePartWantPaths = null;
switch (nextCompositeOf.getParamType()) {
case NUMBER:
paramsListForCompositePart = theNumberParams;
break;
case DATE:
paramsListForCompositePart = theDateParams;
break;
case STRING:
paramsListForCompositePart = theStringParams;
break;
case TOKEN:
paramsListForCompositePart = theTokenParams;
break;
case REFERENCE:
linksForCompositePart = theLinks;
linksForCompositePartWantPaths = new HashSet<>();
linksForCompositePartWantPaths.addAll(nextCompositeOf.getPathsSplit());
break;
case QUANTITY:
paramsListForCompositePart = theQuantityParams;
break;
case URI:
paramsListForCompositePart = theUriParams;
break;
case COMPOSITE:
case HAS:
break;
}
ArrayList<String> nextChoicesList = new ArrayList<>();
partsChoices.add(nextChoicesList);
String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
if (paramsListForCompositePart != null) {
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
if (nextParam.getParamName().equals(nextCompositeOf.getName())) {
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
String value = nextParamAsClientParam.getValueAsQueryToken(myIndexingService.getContext());
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
}
if (linksForCompositePart != null) {
for (ResourceLink nextLink : linksForCompositePart) {
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
}
}
Set<String> queryStringsToPopulate = extractCompositeStringUniquesValueChains(theEntity.getResourceType(), partsChoices);
for (String nextQueryString : queryStringsToPopulate) {
if (isNotBlank(nextQueryString)) {
compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString));
}
}
}
return compositeStringUniques;
} }
private void setUpdatedTime(Collection<? extends BaseResourceIndexedSearchParam> theParams, Date theUpdateTime) {
for (BaseResourceIndexedSearchParam nextSearchParam : theParams) {
nextSearchParam.setUpdated(theUpdateTime);
}
}
/** /**
* This method is used to create a set of all possible combinations of * This method is used to create a set of all possible combinations of
* parameters across a set of search parameters. An example of why * parameters across a set of search parameters. An example of why
@ -531,342 +209,163 @@ public class ResourceIndexedSearchParams {
} }
/**
* @return Returns a set containing all of the parameter names that
* were found to have a value
*/
@SuppressWarnings("unchecked")
protected Set<String> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Collection<ResourceLink> theLinks, Date theUpdateTime) {
HashSet<String> retVal = new HashSet<>();
String resourceType = theEntity.getResourceType();
/* void calculateHashes(Collection<? extends BaseResourceIndexedSearchParam> theStringParams) {
* For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
*/
if (theResource instanceof IBaseBundle) {
return Collections.emptySet();
}
Map<String, RuntimeSearchParam> searchParams = myIndexingService.getSearchParamRegistry().getActiveSearchParams(myIndexingService.toResourceName(theResource.getClass()));
for (RuntimeSearchParam nextSpDef : searchParams.values()) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
continue;
}
String nextPathsUnsplit = nextSpDef.getPath();
if (isBlank(nextPathsUnsplit)) {
continue;
}
boolean multiType = false;
if (nextPathsUnsplit.endsWith("[x]")) {
multiType = true;
}
List<PathAndRef> refs = myIndexingService.getSearchParamExtractor().extractResourceLinks(theResource, nextSpDef);
for (PathAndRef nextPathAndRef : refs) {
Object nextObject = nextPathAndRef.getRef();
/*
* A search parameter on an extension field that contains
* references should index those references
*/
if (nextObject instanceof IBaseExtension<?, ?>) {
nextObject = ((IBaseExtension<?, ?>) nextObject).getValue();
}
if (nextObject instanceof CanonicalType) {
nextObject = new Reference(((CanonicalType) nextObject).getValueAsString());
}
IIdType nextId;
if (nextObject instanceof IBaseReference) {
IBaseReference nextValue = (IBaseReference) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextId = nextValue.getReferenceElement();
/*
* This can only really happen if the DAO is being called
* programatically with a Bundle (not through the FHIR REST API)
* but Smile does this
*/
if (nextId.isEmpty() && nextValue.getResource() != null) {
nextId = nextValue.getResource().getIdElement();
}
if (nextId.isEmpty() || nextId.getValue().startsWith("#")) {
// This is a blank or contained resource reference
continue;
}
} else if (nextObject instanceof IBaseResource) {
nextId = ((IBaseResource) nextObject).getIdElement();
if (nextId == null || nextId.hasIdPart() == false) {
continue;
}
} else if (myIndexingService.getContext().getElementDefinition((Class<? extends IBase>) nextObject.getClass()).getName().equals("uri")) {
continue;
} else if (resourceType.equals("Consent") && nextPathAndRef.getPath().equals("Consent.source")) {
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
continue;
} else {
if (!multiType) {
if (nextSpDef.getName().equals("sourceuri")) {
continue; // TODO: disable this eventually - ConceptMap:sourceuri is of type reference but points to a URI
}
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
} else {
continue;
}
}
retVal.add(nextSpDef.getName());
if (myIndexingService.isLogicalReference(nextId)) {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
if (theLinks.add(resourceLink)) {
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
}
continue;
}
String baseUrl = nextId.getBaseUrl();
String typeString = nextId.getResourceType();
if (isBlank(typeString)) {
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextId.getValue());
}
RuntimeResourceDefinition resourceDefinition;
try {
resourceDefinition = myIndexingService.getContext().getResourceDefinition(typeString);
} catch (DataFormatException e) {
throw new InvalidRequestException(
"Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextId.getValue());
}
if (isNotBlank(baseUrl)) {
if (!myIndexingService.getConfig().getTreatBaseUrlsAsLocal().contains(baseUrl) && !myIndexingService.getConfig().isAllowExternalReferences()) {
String msg = myIndexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "externalReferenceNotAllowed", nextId.getValue());
throw new InvalidRequestException(msg);
} else {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
if (theLinks.add(resourceLink)) {
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
}
continue;
}
}
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
String id = nextId.getIdPart();
if (StringUtils.isBlank(id)) {
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource ID - " + nextId.getValue());
}
IFhirResourceDao<?> dao = myIndexingService.getDao(type);
if (dao == null) {
StringBuilder b = new StringBuilder();
b.append("This server (version ");
b.append(myIndexingService.getContext().getVersion().getVersion());
b.append(") is not able to handle resources of type[");
b.append(nextId.getResourceType());
b.append("] - Valid resource types for this server: ");
b.append(myIndexingService.getResourceTypeToDao().keySet().toString());
throw new InvalidRequestException(b.toString());
}
Long valueOf;
try {
valueOf = myIndexingService.translateForcedIdToPid(typeString, id);
} catch (ResourceNotFoundException e) {
if (myIndexingService.getConfig().isEnforceReferentialIntegrityOnWrite() == false) {
continue;
}
RuntimeResourceDefinition missingResourceDef = myIndexingService.getContext().getResourceDefinition(type);
String resName = missingResourceDef.getName();
if (myIndexingService.getConfig().isAutoCreatePlaceholderReferenceTargets()) {
IBaseResource newResource = missingResourceDef.newInstance();
newResource.setId(resName + "/" + id);
IFhirResourceDao<IBaseResource> placeholderResourceDao = (IFhirResourceDao<IBaseResource>) myIndexingService.getDao(newResource.getClass());
ourLog.debug("Automatically creating empty placeholder resource: {}", newResource.getIdElement().getValue());
valueOf = placeholderResourceDao.update(newResource).getEntity().getId();
} else {
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
}
}
ResourceTable target = myIndexingService.getEntityManager().find(ResourceTable.class, valueOf);
RuntimeResourceDefinition targetResourceDef = myIndexingService.getContext().getResourceDefinition(type);
if (target == null) {
String resName = targetResourceDef.getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
}
if (!typeString.equals(target.getResourceType())) {
throw new UnprocessableEntityException(
"Resource contains reference to " + nextId.getValue() + " but resource with ID " + nextId.getIdPart() + " is actually of type " + target.getResourceType());
}
if (target.getDeleted() != null) {
String resName = targetResourceDef.getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " is deleted, specified in path: " + nextPathsUnsplit);
}
if (nextSpDef.getTargets() != null && !nextSpDef.getTargets().contains(typeString)) {
continue;
}
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, target, theUpdateTime);
theLinks.add(resourceLink);
}
}
theEntity.setHasLinks(theLinks.size() > 0);
return retVal;
}
private void setUpdatedTime(Collection<? extends BaseResourceIndexedSearchParam> theParams, Date theUpdateTime) {
for (BaseResourceIndexedSearchParam nextSearchParam : theParams) {
nextSearchParam.setUpdated(theUpdateTime);
}
}
private String translatePidIdToForcedId(String theResourceType, Long theId) {
ForcedId forcedId = myIndexingService.getForcedIdDao().findByResourcePid(theId);
if (forcedId != null) {
return forcedId.getResourceType() + '/' + forcedId.getForcedId();
} else {
return theResourceType + '/' + theId.toString();
}
}
public void removeCommon(ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
EntityManager myEntityManager = myIndexingService.getEntityManager();
calculateHashes(stringParams);
for (ResourceIndexedSearchParamString next : removeCommon(existingParams.stringParams, stringParams)) {
next.setDaoConfig(myIndexingService.getConfig());
myEntityManager .remove(next);
theEntity.getParamsString().remove(next);
}
for (ResourceIndexedSearchParamString next : removeCommon(stringParams, existingParams.stringParams)) {
myEntityManager.persist(next);
}
calculateHashes(tokenParams);
for (ResourceIndexedSearchParamToken next : removeCommon(existingParams.tokenParams, tokenParams)) {
myEntityManager.remove(next);
theEntity.getParamsToken().remove(next);
}
for (ResourceIndexedSearchParamToken next : removeCommon(tokenParams, existingParams.tokenParams)) {
myEntityManager.persist(next);
}
calculateHashes(numberParams);
for (ResourceIndexedSearchParamNumber next : removeCommon(existingParams.numberParams, numberParams)) {
myEntityManager.remove(next);
theEntity.getParamsNumber().remove(next);
}
for (ResourceIndexedSearchParamNumber next : removeCommon(numberParams, existingParams.numberParams)) {
myEntityManager.persist(next);
}
calculateHashes(quantityParams);
for (ResourceIndexedSearchParamQuantity next : removeCommon(existingParams.quantityParams, quantityParams)) {
myEntityManager.remove(next);
theEntity.getParamsQuantity().remove(next);
}
for (ResourceIndexedSearchParamQuantity next : removeCommon(quantityParams, existingParams.quantityParams)) {
myEntityManager.persist(next);
}
// Store date SP's
calculateHashes(dateParams);
for (ResourceIndexedSearchParamDate next : removeCommon(existingParams.dateParams, dateParams)) {
myEntityManager.remove(next);
theEntity.getParamsDate().remove(next);
}
for (ResourceIndexedSearchParamDate next : removeCommon(dateParams, existingParams.dateParams)) {
myEntityManager.persist(next);
}
// Store URI SP's
calculateHashes(uriParams);
for (ResourceIndexedSearchParamUri next : removeCommon(existingParams.uriParams, uriParams)) {
myEntityManager.remove(next);
theEntity.getParamsUri().remove(next);
}
for (ResourceIndexedSearchParamUri next : removeCommon(uriParams, existingParams.uriParams)) {
myEntityManager.persist(next);
}
// Store Coords SP's
calculateHashes(coordsParams);
for (ResourceIndexedSearchParamCoords next : removeCommon(existingParams.coordsParams, coordsParams)) {
myEntityManager.remove(next);
theEntity.getParamsCoords().remove(next);
}
for (ResourceIndexedSearchParamCoords next : removeCommon(coordsParams, existingParams.coordsParams)) {
myEntityManager.persist(next);
}
// Store resource links
for (ResourceLink next : removeCommon(existingParams.links, links)) {
myEntityManager.remove(next);
theEntity.getResourceLinks().remove(next);
}
for (ResourceLink next : removeCommon(links, existingParams.links)) {
myEntityManager.persist(next);
}
// make sure links are indexed
theEntity.setResourceLinks(links);
// Store composite string uniques
if (myIndexingService.getConfig().isUniqueIndexesEnabled()) {
for (ResourceIndexedCompositeStringUnique next : removeCommon(existingParams.compositeStringUniques, compositeStringUniques)) {
ourLog.debug("Removing unique index: {}", next);
myEntityManager.remove(next);
theEntity.getParamsCompositeStringUnique().remove(next);
}
for (ResourceIndexedCompositeStringUnique next : removeCommon(compositeStringUniques, existingParams.compositeStringUniques)) {
if (myIndexingService.getConfig().isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedCompositeStringUnique existing = myIndexingService.getResourceIndexedCompositeStringUniqueDao().findByQueryString(next.getIndexString());
if (existing != null) {
String msg = myIndexingService.getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "uniqueIndexConflictFailure", theEntity.getResourceType(), next.getIndexString(), existing.getResource().getIdDt().toUnqualifiedVersionless().getValue());
throw new PreconditionFailedException(msg);
}
}
ourLog.debug("Persisting unique index: {}", next);
myEntityManager.persist(next);
}
}
}
private void calculateHashes(Collection<? extends BaseResourceIndexedSearchParam> theStringParams) {
for (BaseResourceIndexedSearchParam next : theStringParams) { for (BaseResourceIndexedSearchParam next : theStringParams) {
next.calculateHashes(); next.calculateHashes();
} }
} }
private <T> Collection<T> removeCommon(Collection<T> theInput, Collection<T> theToRemove) {
assert theInput != theToRemove;
if (theInput.isEmpty()) {
return theInput;
}
ArrayList<T> retVal = new ArrayList<>(theInput);
retVal.removeAll(theToRemove);
return retVal;
}
public Set<String> getPopulatedResourceLinkParameters() { public Set<String> getPopulatedResourceLinkParameters() {
return populatedResourceLinkParameters; return populatedResourceLinkParameters;
} }
public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam paramDef, IQueryParameterType theParam) {
if (paramDef == null) {
return false;
}
Collection<? extends BaseResourceIndexedSearchParam> resourceParams;
switch (paramDef.getParamType()) {
case TOKEN:
resourceParams = tokenParams;
break;
case QUANTITY:
resourceParams = quantityParams;
break;
case STRING:
resourceParams = stringParams;
break;
case NUMBER:
resourceParams = numberParams;
break;
case URI:
resourceParams = uriParams;
break;
case DATE:
resourceParams = dateParams;
break;
case REFERENCE:
return matchResourceLinks(theResourceName, theParamName, theParam);
case COMPOSITE:
case HAS:
case SPECIAL:
default:
resourceParams = null;
}
if (resourceParams == null) {
return false;
}
Predicate<BaseResourceIndexedSearchParam> namedParamPredicate = param ->
param.getParamName().equalsIgnoreCase(theParamName) &&
param.matches(theParam);
return resourceParams.stream().anyMatch(namedParamPredicate);
}
private boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam) {
ReferenceParam reference = (ReferenceParam)theParam;
Predicate<ResourceLink> namedParamPredicate = resourceLink ->
resourceLinkMatches(theResourceName, resourceLink, theParamName)
&& resourceIdMatches(resourceLink, reference);
return links.stream().anyMatch(namedParamPredicate);
}
private boolean resourceIdMatches(ResourceLink theResourceLink, ReferenceParam theReference) {
ResourceTable target = theResourceLink.getTargetResource();
IdDt idDt = target.getIdDt();
if (idDt.isIdPartValidLong()) {
return theReference.getIdPartAsLong() == idDt.getIdPartAsLong();
} else {
ForcedId forcedId = target.getForcedId();
if (forcedId != null) {
return forcedId.getForcedId().equals(theReference.getValue());
} else {
return false;
}
}
}
private boolean resourceLinkMatches(String theResourceName, ResourceLink theResourceLink, String theParamName) {
return theResourceLink.getTargetResource().getResourceType().equalsIgnoreCase(theParamName) ||
theResourceLink.getSourcePath().equalsIgnoreCase(theResourceName+"."+theParamName);
}
@Override
public String toString() {
return "ResourceIndexedSearchParams{" +
"stringParams=" + stringParams +
", tokenParams=" + tokenParams +
", numberParams=" + numberParams +
", quantityParams=" + quantityParams +
", dateParams=" + dateParams +
", uriParams=" + uriParams +
", coordsParams=" + coordsParams +
", compositeStringUniques=" + compositeStringUniques +
", links=" + links +
'}';
}
void findMissingSearchParams(DaoConfig theDaoConfig, ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> theActiveSearchParams) {
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, stringParams);
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams);
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams);
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, dateParams);
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, uriParams);
findMissingSearchParams(theDaoConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams);
}
@SuppressWarnings("unchecked")
private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(DaoConfig theDaoConfig, ResourceTable theEntity, Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type,
Collection<RT> paramCollection) {
for (Map.Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
String nextParamName = nextEntry.getKey();
if (nextEntry.getValue().getParamType() == type) {
boolean haveParam = false;
for (BaseResourceIndexedSearchParam nextParam : paramCollection) {
if (nextParam.getParamName().equals(nextParamName)) {
haveParam = true;
break;
}
}
if (!haveParam) {
BaseResourceIndexedSearchParam param;
switch (type) {
case DATE:
param = new ResourceIndexedSearchParamDate();
break;
case NUMBER:
param = new ResourceIndexedSearchParamNumber();
break;
case QUANTITY:
param = new ResourceIndexedSearchParamQuantity();
break;
case STRING:
param = new ResourceIndexedSearchParamString()
.setDaoConfig(theDaoConfig);
break;
case TOKEN:
param = new ResourceIndexedSearchParamToken();
break;
case URI:
param = new ResourceIndexedSearchParamUri();
break;
case COMPOSITE:
case HAS:
case REFERENCE:
default:
continue;
}
param.setResource(theEntity);
param.setMissing(true);
param.setParamName(nextParamName);
paramCollection.add((RT) param);
}
}
}
}
} }

View File

@ -0,0 +1,604 @@
package ca.uhn.fhir.jpa.dao.index;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.MatchUrlService;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Service
@Lazy
public class SearchParamExtractorService {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorService.class);
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private FhirContext myContext;
@Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private IdHelperService myIdHelperService;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private MatchUrlService myMatchUrlService;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
public void populateFromResource(ResourceIndexedSearchParams theParams, IDao theCallingDao, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams existingParams) {
extractFromResource(theParams, theEntity, theResource);
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
theParams.findMissingSearchParams(myDaoConfig, theEntity, activeSearchParams);
}
theParams.setUpdatedTime(theUpdateTime);
extractInlineReferences(theResource);
extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, true);
/*
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
*/
for (Iterator<ResourceLink> existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) {
ResourceLink nextExisting = existingLinkIter.next();
if (theParams.links.remove(nextExisting)) {
existingLinkIter.remove();
theParams.links.add(nextExisting);
}
}
/*
* Handle composites
*/
extractCompositeStringUniques(theEntity, theParams);
}
public void extractFromResource(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource) {
theParams.stringParams.addAll(extractSearchParamStrings(theEntity, theResource));
theParams.numberParams.addAll(extractSearchParamNumber(theEntity, theResource));
theParams.quantityParams.addAll(extractSearchParamQuantity(theEntity, theResource));
theParams.dateParams.addAll(extractSearchParamDates(theEntity, theResource));
theParams.uriParams.addAll(extractSearchParamUri(theEntity, theResource));
theParams.coordsParams.addAll(extractSearchParamCoords(theEntity, theResource));
ourLog.trace("Storing date indexes: {}", theParams.dateParams);
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) {
if (next instanceof ResourceIndexedSearchParamToken) {
theParams.tokenParams.add((ResourceIndexedSearchParamToken) next);
} else {
theParams.stringParams.add((ResourceIndexedSearchParamString) next);
}
}
}
/**
* 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.
*/
public void extractInlineReferences(IBaseResource theResource) {
if (!myDaoConfig.isAllowInlineMatchUrlReferences()) {
return;
}
FhirTerser terser = myContext.newTerser();
List<IBaseReference> allRefs = terser.getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
for (IBaseReference nextRef : allRefs) {
IIdType nextId = nextRef.getReferenceElement();
String nextIdText = nextId.getValue();
if (nextIdText == null) {
continue;
}
int qmIndex = nextIdText.indexOf('?');
if (qmIndex != -1) {
for (int i = qmIndex - 1; i >= 0; i--) {
if (nextIdText.charAt(i) == '/') {
if (i < nextIdText.length() - 1 && nextIdText.charAt(i + 1) == '?') {
// Just in case the URL is in the form Patient/?foo=bar
continue;
}
nextIdText = nextIdText.substring(i + 1);
break;
}
}
String resourceTypeString = nextIdText.substring(0, nextIdText.indexOf('?')).replace("/", "");
RuntimeResourceDefinition matchResourceDef = myContext.getResourceDefinition(resourceTypeString);
if (matchResourceDef == null) {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlInvalidResourceType", nextId.getValue(), resourceTypeString);
throw new InvalidRequestException(msg);
}
Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
Set<Long> matches = myMatchUrlService.processMatchUrl(nextIdText, matchResourceType);
if (matches.isEmpty()) {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
throw new ResourceNotFoundException(msg);
}
if (matches.size() > 1) {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue());
throw new PreconditionFailedException(msg);
}
Long next = matches.iterator().next();
String newId = myIdHelperService.translatePidIdToForcedId(resourceTypeString, next);
ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
nextRef.setReference(newId);
}
}
}
protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamDates(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamNumber(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamQuantity(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamStrings(theEntity, theResource);
}
protected Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamTokens(theEntity, theResource);
}
protected Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamUri(theEntity, theResource);
}
private void extractCompositeStringUniques(ResourceTable theEntity, ResourceIndexedSearchParams theParams) {
String resourceType = theEntity.getResourceType();
List<JpaRuntimeSearchParam> uniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(resourceType);
for (JpaRuntimeSearchParam next : uniqueSearchParams) {
List<List<String>> partsChoices = new ArrayList<>();
for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
Collection<ResourceLink> linksForCompositePart = null;
Collection<String> linksForCompositePartWantPaths = null;
switch (nextCompositeOf.getParamType()) {
case NUMBER:
paramsListForCompositePart = theParams.numberParams;
break;
case DATE:
paramsListForCompositePart = theParams.dateParams;
break;
case STRING:
paramsListForCompositePart = theParams.stringParams;
break;
case TOKEN:
paramsListForCompositePart = theParams.tokenParams;
break;
case REFERENCE:
linksForCompositePart = theParams.links;
linksForCompositePartWantPaths = new HashSet<>();
linksForCompositePartWantPaths.addAll(nextCompositeOf.getPathsSplit());
break;
case QUANTITY:
paramsListForCompositePart = theParams.quantityParams;
break;
case URI:
paramsListForCompositePart = theParams.uriParams;
break;
case COMPOSITE:
case HAS:
break;
}
ArrayList<String> nextChoicesList = new ArrayList<>();
partsChoices.add(nextChoicesList);
String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
if (paramsListForCompositePart != null) {
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
if (nextParam.getParamName().equals(nextCompositeOf.getName())) {
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
String value = nextParamAsClientParam.getValueAsQueryToken(myContext);
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
}
if (linksForCompositePart != null) {
for (ResourceLink nextLink : linksForCompositePart) {
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
}
}
Set<String> queryStringsToPopulate = theParams.extractCompositeStringUniquesValueChains(resourceType, partsChoices);
for (String nextQueryString : queryStringsToPopulate) {
if (isNotBlank(nextQueryString)) {
theParams.compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString));
}
}
}
}
@SuppressWarnings("unchecked")
public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, boolean lookUpReferencesInDatabase) {
String resourceType = theEntity.getResourceType();
/*
* For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
*/
if (theResource instanceof IBaseBundle) {
return;
}
Map<String, RuntimeSearchParam> searchParams = mySearchParamRegistry.getActiveSearchParams(toResourceName(theResource.getClass()));
for (RuntimeSearchParam nextSpDef : searchParams.values()) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
continue;
}
String nextPathsUnsplit = nextSpDef.getPath();
if (isBlank(nextPathsUnsplit)) {
continue;
}
boolean multiType = false;
if (nextPathsUnsplit.endsWith("[x]")) {
multiType = true;
}
List<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource, nextSpDef);
for (PathAndRef nextPathAndRef : refs) {
Object nextObject = nextPathAndRef.getRef();
/*
* A search parameter on an extension field that contains
* references should index those references
*/
if (nextObject instanceof IBaseExtension<?, ?>) {
nextObject = ((IBaseExtension<?, ?>) nextObject).getValue();
}
if (nextObject instanceof CanonicalType) {
nextObject = new Reference(((CanonicalType) nextObject).getValueAsString());
}
IIdType nextId;
if (nextObject instanceof IBaseReference) {
IBaseReference nextValue = (IBaseReference) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextId = nextValue.getReferenceElement();
/*
* This can only really happen if the DAO is being called
* programatically with a Bundle (not through the FHIR REST API)
* but Smile does this
*/
if (nextId.isEmpty() && nextValue.getResource() != null) {
nextId = nextValue.getResource().getIdElement();
}
if (nextId.isEmpty() || nextId.getValue().startsWith("#")) {
// This is a blank or contained resource reference
continue;
}
} else if (nextObject instanceof IBaseResource) {
nextId = ((IBaseResource) nextObject).getIdElement();
if (nextId == null || nextId.hasIdPart() == false) {
continue;
}
} else if (myContext.getElementDefinition((Class<? extends IBase>) nextObject.getClass()).getName().equals("uri")) {
continue;
} else if (resourceType.equals("Consent") && nextPathAndRef.getPath().equals("Consent.source")) {
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
continue;
} else {
if (!multiType) {
if (nextSpDef.getName().equals("sourceuri")) {
continue; // TODO: disable this eventually - ConceptMap:sourceuri is of type reference but points to a URI
}
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
} else {
continue;
}
}
theParams.populatedResourceLinkParameters.add(nextSpDef.getName());
if (LogicalReferenceHelper.isLogicalReference(myDaoConfig, nextId)) {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
if (theParams.links.add(resourceLink)) {
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
}
continue;
}
String baseUrl = nextId.getBaseUrl();
String typeString = nextId.getResourceType();
if (isBlank(typeString)) {
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextId.getValue());
}
RuntimeResourceDefinition resourceDefinition;
try {
resourceDefinition = myContext.getResourceDefinition(typeString);
} catch (DataFormatException e) {
throw new InvalidRequestException(
"Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextId.getValue());
}
if (isNotBlank(baseUrl)) {
if (!myDaoConfig.getTreatBaseUrlsAsLocal().contains(baseUrl) && !myDaoConfig.isAllowExternalReferences()) {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "externalReferenceNotAllowed", nextId.getValue());
throw new InvalidRequestException(msg);
} else {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
if (theParams.links.add(resourceLink)) {
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
}
continue;
}
}
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
String id = nextId.getIdPart();
if (StringUtils.isBlank(id)) {
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource ID - " + nextId.getValue());
}
myDaoRegistry.getDaoOrThrowException(type);
ResourceTable target;
if (lookUpReferencesInDatabase) {
Long valueOf;
try {
valueOf = myIdHelperService.translateForcedIdToPid(typeString, id);
} catch (ResourceNotFoundException e) {
if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) {
continue;
}
RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(type);
String resName = missingResourceDef.getName();
if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
IBaseResource newResource = missingResourceDef.newInstance();
newResource.setId(resName + "/" + id);
IFhirResourceDao<IBaseResource> placeholderResourceDao = (IFhirResourceDao<IBaseResource>) myDaoRegistry.getResourceDao(newResource.getClass());
ourLog.debug("Automatically creating empty placeholder resource: {}", newResource.getIdElement().getValue());
valueOf = placeholderResourceDao.update(newResource).getEntity().getId();
} else {
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
}
}
target = myEntityManager.find(ResourceTable.class, valueOf);
RuntimeResourceDefinition targetResourceDef = myContext.getResourceDefinition(type);
if (target == null) {
String resName = targetResourceDef.getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
}
if (!typeString.equals(target.getResourceType())) {
throw new UnprocessableEntityException(
"Resource contains reference to " + nextId.getValue() + " but resource with ID " + nextId.getIdPart() + " is actually of type " + target.getResourceType());
}
if (target.getDeleted() != null) {
String resName = targetResourceDef.getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " is deleted, specified in path: " + nextPathsUnsplit);
}
if (nextSpDef.getTargets() != null && !nextSpDef.getTargets().contains(typeString)) {
continue;
}
} else {
target = new ResourceTable();
target.setResourceType(typeString);
if (nextId.isIdPartValidLong()) {
target.setId(nextId.getIdPartAsLong());
} else {
ForcedId forcedId = new ForcedId();
forcedId.setForcedId(id);
target.setForcedId(forcedId);
}
}
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, target, theUpdateTime);
theParams.links.add(resourceLink);
}
}
theEntity.setHasLinks(theParams.links.size() > 0);
}
public String toResourceName(Class<? extends IBaseResource> theResourceType) {
return myContext.getResourceDefinition(theResourceType).getName();
}
public void removeCommon(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
theParams.calculateHashes(theParams.stringParams);
for (ResourceIndexedSearchParamString next : removeCommon(existingParams.stringParams, theParams.stringParams)) {
next.setDaoConfig(myDaoConfig);
myEntityManager.remove(next);
theEntity.getParamsString().remove(next);
}
for (ResourceIndexedSearchParamString next : removeCommon(theParams.stringParams, existingParams.stringParams)) {
myEntityManager.persist(next);
}
theParams.calculateHashes(theParams.tokenParams);
for (ResourceIndexedSearchParamToken next : removeCommon(existingParams.tokenParams, theParams.tokenParams)) {
myEntityManager.remove(next);
theEntity.getParamsToken().remove(next);
}
for (ResourceIndexedSearchParamToken next : removeCommon(theParams.tokenParams, existingParams.tokenParams)) {
myEntityManager.persist(next);
}
theParams.calculateHashes(theParams.numberParams);
for (ResourceIndexedSearchParamNumber next : removeCommon(existingParams.numberParams, theParams.numberParams)) {
myEntityManager.remove(next);
theEntity.getParamsNumber().remove(next);
}
for (ResourceIndexedSearchParamNumber next : removeCommon(theParams.numberParams, existingParams.numberParams)) {
myEntityManager.persist(next);
}
theParams.calculateHashes(theParams.quantityParams);
for (ResourceIndexedSearchParamQuantity next : removeCommon(existingParams.quantityParams, theParams.quantityParams)) {
myEntityManager.remove(next);
theEntity.getParamsQuantity().remove(next);
}
for (ResourceIndexedSearchParamQuantity next : removeCommon(theParams.quantityParams, existingParams.quantityParams)) {
myEntityManager.persist(next);
}
// Store date SP's
theParams.calculateHashes(theParams.dateParams);
for (ResourceIndexedSearchParamDate next : removeCommon(existingParams.dateParams, theParams.dateParams)) {
myEntityManager.remove(next);
theEntity.getParamsDate().remove(next);
}
for (ResourceIndexedSearchParamDate next : removeCommon(theParams.dateParams, existingParams.dateParams)) {
myEntityManager.persist(next);
}
// Store URI SP's
theParams.calculateHashes(theParams.uriParams);
for (ResourceIndexedSearchParamUri next : removeCommon(existingParams.uriParams, theParams.uriParams)) {
myEntityManager.remove(next);
theEntity.getParamsUri().remove(next);
}
for (ResourceIndexedSearchParamUri next : removeCommon(theParams.uriParams, existingParams.uriParams)) {
myEntityManager.persist(next);
}
// Store Coords SP's
theParams.calculateHashes(theParams.coordsParams);
for (ResourceIndexedSearchParamCoords next : removeCommon(existingParams.coordsParams, theParams.coordsParams)) {
myEntityManager.remove(next);
theEntity.getParamsCoords().remove(next);
}
for (ResourceIndexedSearchParamCoords next : removeCommon(theParams.coordsParams, existingParams.coordsParams)) {
myEntityManager.persist(next);
}
// Store resource links
for (ResourceLink next : removeCommon(existingParams.links, theParams.links)) {
myEntityManager.remove(next);
theEntity.getResourceLinks().remove(next);
}
for (ResourceLink next : removeCommon(theParams.links, existingParams.links)) {
myEntityManager.persist(next);
}
// make sure links are indexed
theEntity.setResourceLinks(theParams.links);
// Store composite string uniques
if (myDaoConfig.isUniqueIndexesEnabled()) {
for (ResourceIndexedCompositeStringUnique next : removeCommon(existingParams.compositeStringUniques, theParams.compositeStringUniques)) {
ourLog.debug("Removing unique index: {}", next);
myEntityManager.remove(next);
theEntity.getParamsCompositeStringUnique().remove(next);
}
for (ResourceIndexedCompositeStringUnique next : removeCommon(theParams.compositeStringUniques, existingParams.compositeStringUniques)) {
if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
if (existing != null) {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "uniqueIndexConflictFailure", theEntity.getResourceType(), next.getIndexString(), existing.getResource().getIdDt().toUnqualifiedVersionless().getValue());
throw new PreconditionFailedException(msg);
}
}
ourLog.debug("Persisting unique index: {}", next);
myEntityManager.persist(next);
}
}
}
private <T> Collection<T> removeCommon(Collection<T> theInput, Collection<T> theToRemove) {
assert theInput != theToRemove;
if (theInput.isEmpty()) {
return theInput;
}
ArrayList<T> retVal = new ArrayList<>(theInput);
retVal.removeAll(theToRemove);
return retVal;
}
}

View File

@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
@ -64,8 +63,6 @@ public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4<CodeSystem> i
@Autowired @Autowired
private ITermCodeSystemDao myCsDao; private ITermCodeSystemDao myCsDao;
@Autowired @Autowired
private IHapiTerminologySvc myTerminologySvc;
@Autowired
private ValidationSupportChain myValidationSupport; private ValidationSupportChain myValidationSupport;
@Override @Override

View File

@ -156,4 +156,7 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
return hashCode.asLong(); return hashCode.asLong();
} }
public boolean matches(IQueryParameterType theParam) {
throw new UnsupportedOperationException("No parameter matcher for "+theParam);
}
} }

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@ -184,4 +185,31 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
b.append("valueHigh", new InstantDt(getValueHigh())); b.append("valueHigh", new InstantDt(getValueHigh()));
return b.build(); return b.build();
} }
@Override
public boolean matches(IQueryParameterType theParam) {
if (!(theParam instanceof DateParam)) {
return false;
}
DateParam date = (DateParam) theParam;
DateRangeParam range = new DateRangeParam(date);
Date lowerBound = range.getLowerBoundAsInstant();
Date upperBound = range.getUpperBoundAsInstant();
if (lowerBound == null && upperBound == null) {
// should never happen
return false;
}
boolean result = true;
if (lowerBound != null) {
result &= (myValueLow.after(lowerBound) || myValueLow.equals(lowerBound));
result &= (myValueHigh.after(lowerBound) || myValueHigh.equals(lowerBound));
}
if (upperBound != null) {
result &= (myValueLow.before(upperBound) || myValueLow.equals(upperBound));
result &= (myValueHigh.before(upperBound) || myValueHigh.equals(upperBound));
}
return result;
}
} }

View File

@ -149,4 +149,13 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
b.append("value", getValue()); b.append("value", getValue());
return b.build(); return b.build();
} }
@Override
public boolean matches(IQueryParameterType theParam) {
if (!(theParam instanceof NumberParam)) {
return false;
}
NumberParam number = (NumberParam)theParam;
return getValue().equals(number.getValue());
}
} }

View File

@ -235,4 +235,37 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return hash(theResourceType, theParamName, theUnits); return hash(theResourceType, theParamName, theUnits);
} }
@Override
public boolean matches(IQueryParameterType theParam) {
if (!(theParam instanceof QuantityParam)) {
return false;
}
QuantityParam quantity = (QuantityParam)theParam;
boolean retval = false;
// Only match on system if it wasn't specified
if (quantity.getSystem() == null && quantity.getUnits() == null) {
if (getValue().equals(quantity.getValue())) {
retval = true;
}
} else if (quantity.getSystem() == null) {
if (getUnits().equalsIgnoreCase(quantity.getUnits()) &&
getValue().equals(quantity.getValue())) {
retval = true;
}
} else if (quantity.getUnits() == null) {
if (getSystem().equalsIgnoreCase(quantity.getSystem()) &&
getValue().equals(quantity.getValue())) {
retval = true;
}
} else {
if (getSystem().equalsIgnoreCase(quantity.getSystem()) &&
getUnits().equalsIgnoreCase(quantity.getUnits()) &&
getValue().equals(quantity.getValue())) {
retval = true;
}
}
return retval;
}
} }

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
@ -294,4 +295,13 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength)); return hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength));
} }
@Override
public boolean matches(IQueryParameterType theParam) {
if (!(theParam instanceof StringParam)) {
return false;
}
StringParam string = (StringParam)theParam;
String normalizedString = BaseHapiFhirDao.normalizeString(string.getValue());
return getValueNormalized().startsWith(normalizedString);
}
} }

View File

@ -251,4 +251,29 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
public static long calculateHashValue(String theResourceType, String theParamName, String theValue) { public static long calculateHashValue(String theResourceType, String theParamName, String theValue) {
return hash(theResourceType, theParamName, trim(theValue)); return hash(theResourceType, theParamName, trim(theValue));
} }
@Override
public boolean matches(IQueryParameterType theParam) {
if (!(theParam instanceof TokenParam)) {
return false;
}
TokenParam token = (TokenParam)theParam;
boolean retval = false;
// Only match on system if it wasn't specified
if (token.getSystem() == null || token.getSystem().isEmpty()) {
if (getValue().equalsIgnoreCase(token.getValue())) {
retval = true;
}
} else if (token.getValue() == null || token.getValue().isEmpty()) {
if (token.getSystem().equalsIgnoreCase(getSystem())) {
retval = true;
}
} else {
if (token.getSystem().equalsIgnoreCase(getSystem()) &&
getValue().equalsIgnoreCase(token.getValue())) {
retval = true;
}
}
return retval;
}
} }

View File

@ -179,4 +179,13 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
return hash(theResourceType, theParamName, theUri); return hash(theResourceType, theParamName, theUri);
} }
@Override
public boolean matches(IQueryParameterType theParam) {
if (!(theParam instanceof UriParam)) {
return false;
}
UriParam uri = (UriParam)theParam;
return getUri().equalsIgnoreCase(uri.getValueNotNull());
}
} }

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.entity;
*/ */
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
@ -53,6 +55,16 @@ public class ResourceReindexJobEntity implements Serializable {
@Column(name = "SUSPENDED_UNTIL", nullable = true) @Column(name = "SUSPENDED_UNTIL", nullable = true)
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date mySuspendedUntil; private Date mySuspendedUntil;
@Column(name = "REINDEX_COUNT", nullable = true)
private Integer myReindexCount;
public Integer getReindexCount() {
return myReindexCount;
}
public void setReindexCount(Integer theReindexCount) {
myReindexCount = theReindexCount;
}
public Date getSuspendedUntil() { public Date getSuspendedUntil() {
return mySuspendedUntil; return mySuspendedUntil;
@ -110,4 +122,20 @@ public class ResourceReindexJobEntity implements Serializable {
public void setDeleted(boolean theDeleted) { public void setDeleted(boolean theDeleted) {
myDeleted = theDeleted; myDeleted = theDeleted;
} }
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("id", myId)
.append("resourceType", myResourceType)
.append("thresholdLow", myThresholdLow)
.append("thresholdHigh", myThresholdHigh);
if (myDeleted) {
b.append("deleted", myDeleted);
}
if (mySuspendedUntil != null) {
b.append("suspendedUntil", mySuspendedUntil);
}
return b.toString();
}
} }

View File

@ -87,10 +87,6 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@Column(name = "RES_ID") @Column(name = "RES_ID")
private Long myId; private Long myId;
@OneToMany(mappedBy = "myTargetResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@OptimisticLock(excluded = true)
private Collection<ResourceLink> myIncomingResourceLinks;
@Column(name = "SP_INDEX_STATUS", nullable = true) @Column(name = "SP_INDEX_STATUS", nullable = true)
@OptimisticLock(excluded = true) @OptimisticLock(excluded = true)
private Long myIndexStatus; private Long myIndexStatus;
@ -181,31 +177,36 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@OptimisticLock(excluded = true) @OptimisticLock(excluded = true)
private Collection<ResourceIndexedCompositeStringUnique> myParamsCompositeStringUnique; private Collection<ResourceIndexedCompositeStringUnique> myParamsCompositeStringUnique;
@IndexedEmbedded
@OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) @OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@IndexedEmbedded()
@OptimisticLock(excluded = true) @OptimisticLock(excluded = true)
private Collection<ResourceLink> myResourceLinks; private Collection<ResourceLink> myResourceLinks;
@OneToMany(mappedBy = "myTargetResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@OptimisticLock(excluded = true)
private Collection<ResourceLink> myResourceLinksAsTarget;
@Column(name = "RES_TYPE", length = RESTYPE_LEN) @Column(name = "RES_TYPE", length = RESTYPE_LEN)
@Field @Field
@OptimisticLock(excluded = true) @OptimisticLock(excluded = true)
private String myResourceType; private String myResourceType;
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@OptimisticLock(excluded = true) @OptimisticLock(excluded = true)
private Collection<SearchParamPresent> mySearchParamPresents; private Collection<SearchParamPresent> mySearchParamPresents;
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@OptimisticLock(excluded = true) @OptimisticLock(excluded = true)
private Set<ResourceTag> myTags; private Set<ResourceTag> myTags;
@Transient @Transient
private transient boolean myUnchangedInCurrentOperation; private transient boolean myUnchangedInCurrentOperation;
@Version @Version
@Column(name = "RES_VER") @Column(name = "RES_VER")
private long myVersion; private long myVersion;
public Collection<ResourceLink> getResourceLinksAsTarget() {
if (myResourceLinksAsTarget == null) {
myResourceLinksAsTarget = new ArrayList<>();
}
return myResourceLinksAsTarget;
}
@Override @Override
public ResourceTag addTag(TagDefinition theTag) { public ResourceTag addTag(TagDefinition theTag) {
for (ResourceTag next : getTags()) { for (ResourceTag next : getTags()) {

View File

@ -64,7 +64,7 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
for (Argument nextArgument : theSearchParams) { for (Argument nextArgument : theSearchParams) {
RuntimeSearchParam searchParam = getSearchParamByName(typeDef, nextArgument.getName()); RuntimeSearchParam searchParam = mySearchParamRegistry.getSearchParamByName(typeDef, nextArgument.getName());
for (Value nextValue : nextArgument.getValues()) { for (Value nextValue : nextArgument.getValues()) {
String value = nextValue.getValue(); String value = nextValue.getValue();

View File

@ -21,133 +21,36 @@ package ca.uhn.fhir.jpa.provider;
*/ */
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.subscription.ISubscriptionTriggeringSvc;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.PostConstruct; import java.util.List;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class SubscriptionTriggeringProvider implements IResourceProvider, ApplicationContextAware {
public class SubscriptionTriggeringProvider implements IResourceProvider {
public static final String RESOURCE_ID = "resourceId"; public static final String RESOURCE_ID = "resourceId";
public static final int DEFAULT_MAX_SUBMIT = 10000;
public static final String SEARCH_URL = "searchUrl"; public static final String SEARCH_URL = "searchUrl";
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class);
private final List<SubscriptionTriggeringJobDetails> myActiveJobs = new ArrayList<>();
@Autowired @Autowired
private FhirContext myFhirContext; private FhirContext myFhirContext;
@Autowired @Autowired
private DaoRegistry myDaoRegistry; private ISubscriptionTriggeringSvc mySubscriptionTriggeringSvc;
private List<BaseSubscriptionInterceptor<?>> mySubscriptionInterceptorList;
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
@Autowired
private ISearchCoordinatorSvc mySearchCoordinatorSvc;
private ApplicationContext myAppCtx;
private ExecutorService myExecutorService;
/**
* Sets the maximum number of resources that will be submitted in a single pass
*/
public void setMaxSubmitPerPass(Integer theMaxSubmitPerPass) {
Integer maxSubmitPerPass = theMaxSubmitPerPass;
if (maxSubmitPerPass == null) {
maxSubmitPerPass = DEFAULT_MAX_SUBMIT;
}
Validate.isTrue(maxSubmitPerPass > 0, "theMaxSubmitPerPass must be > 0");
myMaxSubmitPerPass = maxSubmitPerPass;
}
@SuppressWarnings("unchecked")
@PostConstruct
public void start() {
mySubscriptionInterceptorList = ObjectUtils.defaultIfNull(mySubscriptionInterceptorList, Collections.emptyList());
mySubscriptionInterceptorList = new ArrayList<>();
Collection values1 = myAppCtx.getBeansOfType(BaseSubscriptionInterceptor.class).values();
Collection<BaseSubscriptionInterceptor<?>> values = (Collection<BaseSubscriptionInterceptor<?>>) values1;
mySubscriptionInterceptorList.addAll(values);
LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(1000);
BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
.namingPattern("SubscriptionTriggering-%d")
.daemon(false)
.priority(Thread.NORM_PRIORITY)
.build();
RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable theRunnable, ThreadPoolExecutor theExecutor) {
ourLog.info("Note: Subscription triggering queue is full ({} elements), waiting for a slot to become available!", executorQueue.size());
StopWatch sw = new StopWatch();
try {
executorQueue.put(theRunnable);
} catch (InterruptedException theE) {
throw new RejectedExecutionException("Task " + theRunnable.toString() +
" rejected from " + theE.toString());
}
ourLog.info("Slot become available after {}ms", sw.getMillis());
}
};
myExecutorService = new ThreadPoolExecutor(
0,
10,
0L,
TimeUnit.MILLISECONDS,
executorQueue,
threadFactory,
rejectedExecutionHandler);
}
@Operation(name = JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) @Operation(name = JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION)
public IBaseParameters triggerSubscription( public IBaseParameters triggerSubscription(
@OperationParam(name = RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED) List<UriParam> theResourceIds, @OperationParam(name = RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED) List<UriParam> theResourceIds,
@OperationParam(name = SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED) List<StringParam> theSearchUrls @OperationParam(name = SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED) List<StringParam> theSearchUrls
) { ) {
return doTriggerSubscription(theResourceIds, theSearchUrls, null); return mySubscriptionTriggeringSvc.triggerSubscription(theResourceIds, theSearchUrls, null);
} }
@Operation(name = JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) @Operation(name = JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION)
@ -156,331 +59,13 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic
@OperationParam(name = RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED) List<UriParam> theResourceIds, @OperationParam(name = RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED) List<UriParam> theResourceIds,
@OperationParam(name = SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED) List<StringParam> theSearchUrls @OperationParam(name = SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED) List<StringParam> theSearchUrls
) { ) {
return mySubscriptionTriggeringSvc.triggerSubscription(theResourceIds, theSearchUrls, theSubscriptionId);
// Throw a 404 if the subscription doesn't exist
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getResourceDao("Subscription");
IIdType subscriptionId = theSubscriptionId;
if (subscriptionId.hasResourceType() == false) {
subscriptionId = subscriptionId.withResourceType("Subscription");
}
subscriptionDao.read(subscriptionId);
return doTriggerSubscription(theResourceIds, theSearchUrls, subscriptionId);
} }
private IBaseParameters doTriggerSubscription(@OperationParam(name = RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED) List<UriParam> theResourceIds, @OperationParam(name = SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED) List<StringParam> theSearchUrls, @IdParam IIdType theSubscriptionId) {
if (mySubscriptionInterceptorList.isEmpty()) {
throw new PreconditionFailedException("Subscription processing not active on this server");
}
List<UriParam> resourceIds = ObjectUtils.defaultIfNull(theResourceIds, Collections.emptyList());
List<StringParam> searchUrls = ObjectUtils.defaultIfNull(theSearchUrls, Collections.emptyList());
// Make sure we have at least one resource ID or search URL
if (resourceIds.size() == 0 && searchUrls.size() == 0) {
throw new InvalidRequestException("No resource IDs or search URLs specified for triggering");
}
// Resource URLs must be compete
for (UriParam next : resourceIds) {
IdType resourceId = new IdType(next.getValue());
ValidateUtil.isTrueOrThrowInvalidRequest(resourceId.hasResourceType(), RESOURCE_ID + " parameter must have resource type");
ValidateUtil.isTrueOrThrowInvalidRequest(resourceId.hasIdPart(), RESOURCE_ID + " parameter must have resource ID part");
}
// Search URLs must be valid
for (StringParam next : searchUrls) {
if (next.getValue().contains("?") == false) {
throw new InvalidRequestException("Search URL is not valid (must be in the form \"[resource type]?[optional params]\")");
}
}
SubscriptionTriggeringJobDetails jobDetails = new SubscriptionTriggeringJobDetails();
jobDetails.setJobId(UUID.randomUUID().toString());
jobDetails.setRemainingResourceIds(resourceIds.stream().map(UriParam::getValue).collect(Collectors.toList()));
jobDetails.setRemainingSearchUrls(searchUrls.stream().map(StringParam::getValue).collect(Collectors.toList()));
if (theSubscriptionId != null) {
jobDetails.setSubscriptionId(theSubscriptionId.toUnqualifiedVersionless().getValue());
}
// Submit job for processing
synchronized (myActiveJobs) {
myActiveJobs.add(jobDetails);
}
ourLog.info("Subscription triggering requested for {} resource and {} search - Gave job ID: {}", resourceIds.size(), searchUrls.size(), jobDetails.getJobId());
// Create a parameters response
IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
IPrimitiveType<?> value = (IPrimitiveType<?>) myFhirContext.getElementDefinition("string").newInstance();
value.setValueAsString("Subscription triggering job submitted as JOB ID: " + jobDetails.myJobId);
ParametersUtil.addParameterToParameters(myFhirContext, retVal, "information", value);
return retVal;
}
@Override @Override
public Class<? extends IBaseResource> getResourceType() { public Class<? extends IBaseResource> getResourceType() {
return myFhirContext.getResourceDefinition("Subscription").getImplementingClass(); return myFhirContext.getResourceDefinition("Subscription").getImplementingClass();
} }
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_SECOND)
public void runDeliveryPass() {
synchronized (myActiveJobs) {
if (myActiveJobs.isEmpty()) {
return;
}
String activeJobIds = myActiveJobs.stream().map(t->t.getJobId()).collect(Collectors.joining(", "));
ourLog.info("Starting pass: currently have {} active job IDs: {}", myActiveJobs.size(), activeJobIds);
SubscriptionTriggeringJobDetails activeJob = myActiveJobs.get(0);
runJob(activeJob);
// If the job is complete, remove it from the queue
if (activeJob.getRemainingResourceIds().isEmpty()) {
if (activeJob.getRemainingSearchUrls().isEmpty()) {
if (isBlank(activeJob.myCurrentSearchUuid)) {
myActiveJobs.remove(0);
String remainingJobsMsg = "";
if (myActiveJobs.size() > 0) {
remainingJobsMsg = "(" + myActiveJobs.size() + " jobs remaining)";
}
ourLog.info("Subscription triggering job {} is complete{}", activeJob.getJobId(), remainingJobsMsg);
}
}
}
}
}
private void runJob(SubscriptionTriggeringJobDetails theJobDetails) {
StopWatch sw = new StopWatch();
ourLog.info("Starting pass of subscription triggering job {}", theJobDetails.getJobId());
// Submit individual resources
int totalSubmitted = 0;
List<Pair<String, Future<Void>>> futures = new ArrayList<>();
while (theJobDetails.getRemainingResourceIds().size() > 0 && totalSubmitted < myMaxSubmitPerPass) {
totalSubmitted++;
String nextResourceId = theJobDetails.getRemainingResourceIds().remove(0);
Future<Void> future = submitResource(theJobDetails.getSubscriptionId(), nextResourceId);
futures.add(Pair.of(nextResourceId, future));
}
// Make sure these all succeeded in submitting
if (validateFuturesAndReturnTrueIfWeShouldAbort(futures)) {
return;
}
// If we don't have an active search started, and one needs to be.. start it
if (isBlank(theJobDetails.getCurrentSearchUuid()) && theJobDetails.getRemainingSearchUrls().size() > 0 && totalSubmitted < myMaxSubmitPerPass) {
String nextSearchUrl = theJobDetails.getRemainingSearchUrls().remove(0);
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myFhirContext, nextSearchUrl);
String queryPart = nextSearchUrl.substring(nextSearchUrl.indexOf('?'));
String resourceType = resourceDef.getName();
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceType);
SearchParameterMap params = BaseHapiFhirDao.translateMatchUrl(callingDao, myFhirContext, queryPart, resourceDef);
ourLog.info("Triggering job[{}] is starting a search for {}", theJobDetails.getJobId(), nextSearchUrl);
IBundleProvider search = mySearchCoordinatorSvc.registerSearch(callingDao, params, resourceType, new CacheControlDirective());
theJobDetails.setCurrentSearchUuid(search.getUuid());
theJobDetails.setCurrentSearchResourceType(resourceType);
theJobDetails.setCurrentSearchCount(params.getCount());
theJobDetails.setCurrentSearchLastUploadedIndex(-1);
}
// If we have an active search going, submit resources from it
if (isNotBlank(theJobDetails.getCurrentSearchUuid()) && totalSubmitted < myMaxSubmitPerPass) {
int fromIndex = theJobDetails.getCurrentSearchLastUploadedIndex() + 1;
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(theJobDetails.getCurrentSearchResourceType());
int maxQuerySize = myMaxSubmitPerPass - totalSubmitted;
int toIndex = fromIndex + maxQuerySize;
if (theJobDetails.getCurrentSearchCount() != null) {
toIndex = Math.min(toIndex, theJobDetails.getCurrentSearchCount());
}
ourLog.info("Triggering job[{}] search {} requesting resources {} - {}", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
List<Long> resourceIds = mySearchCoordinatorSvc.getResources(theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
ourLog.info("Triggering job[{}] delivering {} resources", theJobDetails.getJobId(), resourceIds.size());
int highestIndexSubmitted = theJobDetails.getCurrentSearchLastUploadedIndex();
for (Long next : resourceIds) {
IBaseResource nextResource = resourceDao.readByPid(next);
Future<Void> future = submitResource(theJobDetails.getSubscriptionId(), nextResource);
futures.add(Pair.of(nextResource.getIdElement().getIdPart(), future));
totalSubmitted++;
highestIndexSubmitted++;
}
if (validateFuturesAndReturnTrueIfWeShouldAbort(futures)) {
return;
}
theJobDetails.setCurrentSearchLastUploadedIndex(highestIndexSubmitted);
if (resourceIds.size() == 0 || (theJobDetails.getCurrentSearchCount() != null && toIndex >= theJobDetails.getCurrentSearchCount())) {
ourLog.info("Triggering job[{}] search {} has completed ", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid());
theJobDetails.setCurrentSearchResourceType(null);
theJobDetails.setCurrentSearchUuid(null);
theJobDetails.setCurrentSearchLastUploadedIndex(-1);
theJobDetails.setCurrentSearchCount(null);
}
}
ourLog.info("Subscription trigger job[{}] triggered {} resources in {}ms ({} res / second)", theJobDetails.getJobId(), totalSubmitted, sw.getMillis(), sw.getThroughput(totalSubmitted, TimeUnit.SECONDS));
}
private boolean validateFuturesAndReturnTrueIfWeShouldAbort(List<Pair<String, Future<Void>>> theIdToFutures) {
for (Pair<String, Future<Void>> next : theIdToFutures) {
String nextDeliveredId = next.getKey();
try {
Future<Void> nextFuture = next.getValue();
nextFuture.get();
ourLog.info("Finished redelivering {}", nextDeliveredId);
} catch (Exception e) {
ourLog.error("Failure triggering resource " + nextDeliveredId, e);
return true;
}
}
// Clear the list since it will potentially get reused
theIdToFutures.clear();
return false;
}
private Future<Void> submitResource(String theSubscriptionId, String theResourceIdToTrigger) {
org.hl7.fhir.r4.model.IdType resourceId = new org.hl7.fhir.r4.model.IdType(theResourceIdToTrigger);
IFhirResourceDao<? extends IBaseResource> dao = myDaoRegistry.getResourceDao(resourceId.getResourceType());
IBaseResource resourceToTrigger = dao.read(resourceId);
return submitResource(theSubscriptionId, resourceToTrigger);
}
private Future<Void> submitResource(String theSubscriptionId, IBaseResource theResourceToTrigger) {
ourLog.info("Submitting resource {} to subscription {}", theResourceToTrigger.getIdElement().toUnqualifiedVersionless().getValue(), theSubscriptionId);
ResourceModifiedMessage msg = new ResourceModifiedMessage();
msg.setId(theResourceToTrigger.getIdElement());
msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.UPDATE);
msg.setSubscriptionId(new IdType(theSubscriptionId).toUnqualifiedVersionless().getValue());
msg.setNewPayload(myFhirContext, theResourceToTrigger);
return myExecutorService.submit(()->{
for (int i = 0; ; i++) {
try {
for (BaseSubscriptionInterceptor<?> next : mySubscriptionInterceptorList) {
next.submitResourceModified(msg);
}
break;
} catch (Exception e) {
if (i >= 3) {
throw new InternalErrorException(e);
}
ourLog.warn("Exception while retriggering subscriptions (going to sleep and retry): {}", e.toString());
Thread.sleep(1000);
}
}
return null;
});
}
public void cancelAll() {
synchronized (myActiveJobs) {
myActiveJobs.clear();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myAppCtx = applicationContext;
}
private static class SubscriptionTriggeringJobDetails {
private String myJobId;
private String mySubscriptionId;
private List<String> myRemainingResourceIds;
private List<String> myRemainingSearchUrls;
private String myCurrentSearchUuid;
private Integer myCurrentSearchCount;
private String myCurrentSearchResourceType;
private int myCurrentSearchLastUploadedIndex;
public Integer getCurrentSearchCount() {
return myCurrentSearchCount;
}
public void setCurrentSearchCount(Integer theCurrentSearchCount) {
myCurrentSearchCount = theCurrentSearchCount;
}
public String getCurrentSearchResourceType() {
return myCurrentSearchResourceType;
}
public void setCurrentSearchResourceType(String theCurrentSearchResourceType) {
myCurrentSearchResourceType = theCurrentSearchResourceType;
}
public String getJobId() {
return myJobId;
}
public void setJobId(String theJobId) {
myJobId = theJobId;
}
public String getSubscriptionId() {
return mySubscriptionId;
}
public void setSubscriptionId(String theSubscriptionId) {
mySubscriptionId = theSubscriptionId;
}
public List<String> getRemainingResourceIds() {
return myRemainingResourceIds;
}
public void setRemainingResourceIds(List<String> theRemainingResourceIds) {
myRemainingResourceIds = theRemainingResourceIds;
}
public List<String> getRemainingSearchUrls() {
return myRemainingSearchUrls;
}
public void setRemainingSearchUrls(List<String> theRemainingSearchUrls) {
myRemainingSearchUrls = theRemainingSearchUrls;
}
public String getCurrentSearchUuid() {
return myCurrentSearchUuid;
}
public void setCurrentSearchUuid(String theCurrentSearchUuid) {
myCurrentSearchUuid = theCurrentSearchUuid;
}
public int getCurrentSearchLastUploadedIndex() {
return myCurrentSearchLastUploadedIndex;
}
public void setCurrentSearchLastUploadedIndex(int theCurrentSearchLastUploadedIndex) {
myCurrentSearchLastUploadedIndex = theCurrentSearchLastUploadedIndex;
}
}
} }

View File

@ -19,21 +19,23 @@ package ca.uhn.fhir.jpa.provider.dstu3;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants; import ca.uhn.fhir.util.ExtensionConstants;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
@ -41,16 +43,16 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
private volatile CapabilityStatement myCachedValue; private volatile CapabilityStatement myCachedValue;
private DaoConfig myDaoConfig; private DaoConfig myDaoConfig;
private ISearchParamRegistry mySearchParamRegistry;
private String myImplementationDescription; private String myImplementationDescription;
private boolean myIncludeResourceCounts; private boolean myIncludeResourceCounts;
private RestfulServer myRestfulServer; private RestfulServer myRestfulServer;
private IFhirSystemDao<Bundle, Meta> mySystemDao; private IFhirSystemDao<Bundle, Meta> mySystemDao;
/** /**
* Constructor * Constructor
*/ */
@CoverageIgnore @CoverageIgnore
public JpaConformanceProviderDstu3(){ public JpaConformanceProviderDstu3() {
super(); super();
super.setCache(false); super.setCache(false);
setIncludeResourceCounts(true); setIncludeResourceCounts(true);
@ -65,9 +67,14 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
mySystemDao = theSystemDao; mySystemDao = theSystemDao;
myDaoConfig = theDaoConfig; myDaoConfig = theDaoConfig;
super.setCache(false); super.setCache(false);
setSearchParamRegistry(theSystemDao.getSearchParamRegistry());
setIncludeResourceCounts(true); setIncludeResourceCounts(true);
} }
public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
mySearchParamRegistry = theSearchParamRegistry;
}
@Override @Override
public CapabilityStatement getServerConformance(HttpServletRequest theRequest) { public CapabilityStatement getServerConformance(HttpServletRequest theRequest) {
CapabilityStatement retVal = myCachedValue; CapabilityStatement retVal = myCachedValue;
@ -99,7 +106,7 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
nextResource.getSearchParam().clear(); nextResource.getSearchParam().clear();
String resourceName = nextResource.getType(); String resourceName = nextResource.getType();
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
Collection<RuntimeSearchParam> searchParams = mySystemDao.getSearchParamsByResourceType(resourceDef); Collection<RuntimeSearchParam> searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef);
for (RuntimeSearchParam runtimeSp : searchParams) { for (RuntimeSearchParam runtimeSp : searchParams) {
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam(); CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
@ -107,33 +114,33 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
confSp.setDocumentation(runtimeSp.getDescription()); confSp.setDocumentation(runtimeSp.getDescription());
confSp.setDefinition(runtimeSp.getUri()); confSp.setDefinition(runtimeSp.getUri());
switch (runtimeSp.getParamType()) { switch (runtimeSp.getParamType()) {
case COMPOSITE: case COMPOSITE:
confSp.setType(SearchParamType.COMPOSITE); confSp.setType(SearchParamType.COMPOSITE);
break; break;
case DATE: case DATE:
confSp.setType(SearchParamType.DATE); confSp.setType(SearchParamType.DATE);
break; break;
case NUMBER: case NUMBER:
confSp.setType(SearchParamType.NUMBER); confSp.setType(SearchParamType.NUMBER);
break; break;
case QUANTITY: case QUANTITY:
confSp.setType(SearchParamType.QUANTITY); confSp.setType(SearchParamType.QUANTITY);
break; break;
case REFERENCE: case REFERENCE:
confSp.setType(SearchParamType.REFERENCE); confSp.setType(SearchParamType.REFERENCE);
break; break;
case STRING: case STRING:
confSp.setType(SearchParamType.STRING); confSp.setType(SearchParamType.STRING);
break; break;
case TOKEN: case TOKEN:
confSp.setType(SearchParamType.TOKEN); confSp.setType(SearchParamType.TOKEN);
break; break;
case URI: case URI:
confSp.setType(SearchParamType.URI); confSp.setType(SearchParamType.URI);
break; break;
case HAS: case HAS:
// Shouldn't happen // Shouldn't happen
break; break;
} }
} }
@ -152,6 +159,10 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
return myIncludeResourceCounts; return myIncludeResourceCounts;
} }
public void setIncludeResourceCounts(boolean theIncludeResourceCounts) {
myIncludeResourceCounts = theIncludeResourceCounts;
}
/** /**
* Subclasses may override * Subclasses may override
*/ */
@ -168,10 +179,6 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
myImplementationDescription = theImplDesc; myImplementationDescription = theImplDesc;
} }
public void setIncludeResourceCounts(boolean theIncludeResourceCounts) {
myIncludeResourceCounts = theIncludeResourceCounts;
}
@Override @Override
public void setRestfulServer(RestfulServer theRestfulServer) { public void setRestfulServer(RestfulServer theRestfulServer) {
this.myRestfulServer = theRestfulServer; this.myRestfulServer = theRestfulServer;

View File

@ -23,6 +23,7 @@ import java.util.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.CapabilityStatement.*; import org.hl7.fhir.r4.model.CapabilityStatement.*;
import org.hl7.fhir.r4.model.Enumerations.SearchParamType; import org.hl7.fhir.r4.model.Enumerations.SearchParamType;
@ -41,6 +42,7 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S
private volatile CapabilityStatement myCachedValue; private volatile CapabilityStatement myCachedValue;
private DaoConfig myDaoConfig; private DaoConfig myDaoConfig;
private ISearchParamRegistry mySearchParamRegistry;
private String myImplementationDescription; private String myImplementationDescription;
private boolean myIncludeResourceCounts; private boolean myIncludeResourceCounts;
private RestfulServer myRestfulServer; private RestfulServer myRestfulServer;
@ -66,6 +68,11 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S
myDaoConfig = theDaoConfig; myDaoConfig = theDaoConfig;
super.setCache(false); super.setCache(false);
setIncludeResourceCounts(true); setIncludeResourceCounts(true);
setSearchParamRegistry(theSystemDao.getSearchParamRegistry());
}
public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
mySearchParamRegistry = theSearchParamRegistry;
} }
@Override @Override
@ -99,7 +106,7 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S
nextResource.getSearchParam().clear(); nextResource.getSearchParam().clear();
String resourceName = nextResource.getType(); String resourceName = nextResource.getType();
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
Collection<RuntimeSearchParam> searchParams = mySystemDao.getSearchParamsByResourceType(resourceDef); Collection<RuntimeSearchParam> searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef);
for (RuntimeSearchParam runtimeSp : searchParams) { for (RuntimeSearchParam runtimeSp : searchParams) {
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam(); CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();

View File

@ -26,7 +26,9 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.BasePagingProvider; import ca.uhn.fhir.rest.server.BasePagingProvider;
import ca.uhn.fhir.rest.server.IPagingProvider; import ca.uhn.fhir.rest.server.IPagingProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DatabaseBackedPagingProvider extends BasePagingProvider implements IPagingProvider { public class DatabaseBackedPagingProvider extends BasePagingProvider implements IPagingProvider {
@Autowired @Autowired

View File

@ -276,8 +276,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
protected List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) { protected List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) {
Set<Long> includedPids = new HashSet<>(); Set<Long> includedPids = new HashSet<>();
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) { if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated(), myUuid)); includedPids.addAll(sb.loadIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated(), myUuid));
includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated(), myUuid)); includedPids.addAll(sb.loadIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated(), myUuid));
} }
// Execute the query and make sure we return distinct results // Execute the query and make sure we return distinct results

View File

@ -47,11 +47,15 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*; import org.springframework.data.domain.AbstractPageRequest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.orm.jpa.JpaDialect; import org.springframework.orm.jpa.JpaDialect;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect; import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
@ -68,6 +72,7 @@ import java.util.concurrent.*;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
@Component("mySearchCoordinatorSvc")
public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
public static final int DEFAULT_SYNC_SIZE = 250; public static final int DEFAULT_SYNC_SIZE = 250;
@ -240,8 +245,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
}); });
} catch (Exception e) { } catch (Exception e) {
ourLog.warn("Failed to activate search: {}", e.toString()); ourLog.warn("Failed to activate search: {}", e.toString());
// FIXME: aaaaa ourLog.trace("Failed to activate search", e);
ourLog.info("Failed to activate search", e);
return Optional.empty(); return Optional.empty();
} }
} }
@ -313,8 +317,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
* individually for pages as we return them to clients * individually for pages as we return them to clients
*/ */
final Set<Long> includedPids = new HashSet<>(); final Set<Long> includedPids = new HashSet<>();
includedPids.addAll(sb.loadIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated(), "(synchronous)")); includedPids.addAll(sb.loadIncludes(myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated(), "(synchronous)"));
includedPids.addAll(sb.loadIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated(), "(synchronous)")); includedPids.addAll(sb.loadIncludes(myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated(), "(synchronous)"));
List<IBaseResource> resources = new ArrayList<>(); List<IBaseResource> resources = new ArrayList<>();
sb.loadResourcesByPid(pids, resources, includedPids, false, myEntityManager, myContext, theCallingDao); sb.loadResourcesByPid(pids, resources, includedPids, false, myEntityManager, myContext, theCallingDao);
@ -513,10 +517,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
* user has requested resources 0-60, then they would get 0-50 back but the search * user has requested resources 0-60, then they would get 0-50 back but the search
* coordinator would then stop searching.SearchCoordinatorSvcImplTest * coordinator would then stop searching.SearchCoordinatorSvcImplTest
*/ */
// FIXME: aaaaaaaa
// List<Long> remainingResources = SearchCoordinatorSvcImpl.this.getResources(mySearch.getUuid(), mySyncedPids.size(), theToIndex);
// ourLog.debug("Adding {} resources to the existing {} synced resource IDs", remainingResources.size(), mySyncedPids.size());
// mySyncedPids.addAll(remainingResources);
keepWaiting = false; keepWaiting = false;
break; break;
case FAILED: case FAILED:

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.dstu3.model.InstantType; import org.hl7.fhir.dstu3.model.InstantType;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -36,13 +37,17 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* Deletes old searches * Deletes old searches
*/ */
//
// NOTE: This is not a @Service because we manually instantiate
// it in BaseConfig. This is so that we can override the definition
// in Smile.
//
public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc { public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
public static final long DEFAULT_CUTOFF_SLACK = 10 * DateUtils.MILLIS_PER_SECOND; public static final long DEFAULT_CUTOFF_SLACK = 10 * DateUtils.MILLIS_PER_SECOND;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcImpl.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcImpl.class);
@ -51,7 +56,10 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
* DELETE FROM foo WHERE params IN (aaaa) * DELETE FROM foo WHERE params IN (aaaa)
* type query and this can fail if we have 1000s of params * type query and this can fail if we have 1000s of params
*/ */
public static int ourMaximumResultsToDelete = 500; public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT = 500;
public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS = 20000;
private static int ourMaximumResultsToDeleteInOneStatement = DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT;
private static int ourMaximumResultsToDeleteInOnePass = DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS;
private static Long ourNowForUnitTests; private static Long ourNowForUnitTests;
/* /*
* We give a bit of extra leeway just to avoid race conditions where a query result * We give a bit of extra leeway just to avoid race conditions where a query result
@ -69,8 +77,6 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
private ISearchResultDao mySearchResultDao; private ISearchResultDao mySearchResultDao;
@Autowired @Autowired
private PlatformTransactionManager myTransactionManager; private PlatformTransactionManager myTransactionManager;
@PersistenceContext()
private EntityManager myEntityManager;
private void deleteSearch(final Long theSearchPid) { private void deleteSearch(final Long theSearchPid) {
mySearchDao.findById(theSearchPid).ifPresent(searchToDelete -> { mySearchDao.findById(theSearchPid).ifPresent(searchToDelete -> {
@ -84,10 +90,14 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
* huge deal to be only partially deleting search results. They'll get deleted * huge deal to be only partially deleting search results. They'll get deleted
* eventually * eventually
*/ */
int max = ourMaximumResultsToDelete; int max = ourMaximumResultsToDeleteInOnePass;
Slice<Long> resultPids = mySearchResultDao.findForSearch(PageRequest.of(0, max), searchToDelete.getId()); Slice<Long> resultPids = mySearchResultDao.findForSearch(PageRequest.of(0, max), searchToDelete.getId());
if (resultPids.hasContent()) { if (resultPids.hasContent()) {
mySearchResultDao.deleteByIds(resultPids.getContent()); List<List<Long>> partitions = Lists.partition(resultPids.getContent(), ourMaximumResultsToDeleteInOneStatement);
for (List<Long> nextPartition : partitions) {
mySearchResultDao.deleteByIds(nextPartition);
}
} }
// Only delete if we don't have results left in this search // Only delete if we don't have results left in this search
@ -158,9 +168,14 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
myCutoffSlack = theCutoffSlack; myCutoffSlack = theCutoffSlack;
} }
@VisibleForTesting
public static void setMaximumResultsToDeleteInOnePassForUnitTest(int theMaximumResultsToDeleteInOnePass) {
ourMaximumResultsToDeleteInOnePass = theMaximumResultsToDeleteInOnePass;
}
@VisibleForTesting @VisibleForTesting
public static void setMaximumResultsToDeleteForUnitTest(int theMaximumResultsToDelete) { public static void setMaximumResultsToDeleteForUnitTest(int theMaximumResultsToDelete) {
ourMaximumResultsToDelete = theMaximumResultsToDelete; ourMaximumResultsToDeleteInOneStatement = theMaximumResultsToDelete;
} }
private static long now() { private static long now() {

View File

@ -24,17 +24,24 @@ public interface IResourceReindexingSvc {
/** /**
* Marks all indexes as needing fresh indexing * Marks all indexes as needing fresh indexing
*
* @return Returns the job ID
*/ */
void markAllResourcesForReindexing(); Long markAllResourcesForReindexing();
/** /**
* Marks all indexes of the given type as needing fresh indexing * Marks all indexes of the given type as needing fresh indexing
*
* @return Returns the job ID
*/ */
void markAllResourcesForReindexing(String theType); Long markAllResourcesForReindexing(String theType);
/** /**
* Called automatically by the job scheduler * Called automatically by the job scheduler
* */
void scheduleReindexingPass();
/**
* @return Returns null if the system did not attempt to perform a pass because one was * @return Returns null if the system did not attempt to perform a pass because one was
* already proceeding. Otherwise, returns the number of resources affected. * already proceeding. Otherwise, returns the number of resources affected.
*/ */

View File

@ -73,6 +73,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
private static final Date BEGINNING_OF_TIME = new Date(0); private static final Date BEGINNING_OF_TIME = new Date(0);
private static final Logger ourLog = LoggerFactory.getLogger(ResourceReindexingSvcImpl.class); private static final Logger ourLog = LoggerFactory.getLogger(ResourceReindexingSvcImpl.class);
private static final int PASS_SIZE = 25000;
private final ReentrantLock myIndexingLock = new ReentrantLock(); private final ReentrantLock myIndexingLock = new ReentrantLock();
@Autowired @Autowired
private IResourceReindexJobDao myReindexJobDao; private IResourceReindexJobDao myReindexJobDao;
@ -149,13 +150,13 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public void markAllResourcesForReindexing() { public Long markAllResourcesForReindexing() {
markAllResourcesForReindexing(null); return markAllResourcesForReindexing(null);
} }
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public void markAllResourcesForReindexing(String theType) { public Long markAllResourcesForReindexing(String theType) {
String typeDesc; String typeDesc;
if (isNotBlank(theType)) { if (isNotBlank(theType)) {
myReindexJobDao.markAllOfTypeAsDeleted(theType); myReindexJobDao.markAllOfTypeAsDeleted(theType);
@ -171,11 +172,19 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
job = myReindexJobDao.saveAndFlush(job); job = myReindexJobDao.saveAndFlush(job);
ourLog.info("Marking all resources of type {} for reindexing - Got job ID[{}]", typeDesc, job.getId()); ourLog.info("Marking all resources of type {} for reindexing - Got job ID[{}]", typeDesc, job.getId());
return job.getId();
} }
@Override @Override
@Transactional(Transactional.TxType.NEVER) @Transactional(Transactional.TxType.NEVER)
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND) @Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
public void scheduleReindexingPass() {
runReindexingPass();
}
@Override
@Transactional(Transactional.TxType.NEVER)
public Integer runReindexingPass() { public Integer runReindexingPass() {
if (myDaoConfig.isSchedulingDisabled()) { if (myDaoConfig.isSchedulingDisabled()) {
return null; return null;
@ -223,10 +232,16 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
Collection<ResourceReindexJobEntity> jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false)); Collection<ResourceReindexJobEntity> jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false));
assert jobs != null; assert jobs != null;
if (jobs.size() > 0) {
ourLog.info("Running {} reindex jobs: {}", jobs.size(), jobs);
} else {
ourLog.debug("Running {} reindex jobs: {}", jobs.size(), jobs);
}
int count = 0; int count = 0;
for (ResourceReindexJobEntity next : jobs) { for (ResourceReindexJobEntity next : jobs) {
if (next.getThresholdHigh().getTime() < System.currentTimeMillis()) { if (next.getThresholdLow() != null && next.getThresholdLow().getTime() >= next.getThresholdHigh().getTime()) {
markJobAsDeleted(next); markJobAsDeleted(next);
continue; continue;
} }
@ -236,9 +251,10 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
return count; return count;
} }
private void markJobAsDeleted(ResourceReindexJobEntity next) { private void markJobAsDeleted(ResourceReindexJobEntity theJob) {
ourLog.info("Marking reindexing job ID[{}] as deleted", theJob.getId());
myTxTemplate.execute(t -> { myTxTemplate.execute(t -> {
myReindexJobDao.markAsDeletedById(next.getId()); myReindexJobDao.markAsDeletedById(theJob.getId());
return null; return null;
}); });
} }
@ -259,8 +275,9 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
Date high = theJob.getThresholdHigh(); Date high = theJob.getThresholdHigh();
// Query for resources within threshold // Query for resources within threshold
StopWatch pageSw = new StopWatch();
Slice<Long> range = myTxTemplate.execute(t -> { Slice<Long> range = myTxTemplate.execute(t -> {
PageRequest page = PageRequest.of(0, 10000); PageRequest page = PageRequest.of(0, PASS_SIZE);
if (isNotBlank(theJob.getResourceType())) { if (isNotBlank(theJob.getResourceType())) {
return myResourceTableDao.findIdsOfResourcesWithinUpdatedRangeOrderedFromOldest(page, theJob.getResourceType(), low, high); return myResourceTableDao.findIdsOfResourcesWithinUpdatedRangeOrderedFromOldest(page, theJob.getResourceType(), low, high);
} else { } else {
@ -269,6 +286,13 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
}); });
Validate.notNull(range); Validate.notNull(range);
int count = range.getNumberOfElements(); int count = range.getNumberOfElements();
ourLog.info("Loaded {} resources for reindexing in {}", count, pageSw.toString());
// If we didn't find any results at all, mark as deleted
if (count == 0) {
markJobAsDeleted(theJob);
return 0;
}
// Submit each resource requiring reindexing // Submit each resource requiring reindexing
List<Future<Date>> futures = range List<Future<Date>> futures = range
@ -277,7 +301,6 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
.collect(Collectors.toList()); .collect(Collectors.toList());
Date latestDate = null; Date latestDate = null;
boolean haveMultipleDates = false;
for (Future<Date> next : futures) { for (Future<Date> next : futures) {
Date nextDate; Date nextDate;
try { try {
@ -293,29 +316,22 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
} }
if (nextDate != null) { if (nextDate != null) {
if (latestDate != null) {
if (latestDate.getTime() != nextDate.getTime()) {
haveMultipleDates = true;
}
}
if (latestDate == null || latestDate.getTime() < nextDate.getTime()) { if (latestDate == null || latestDate.getTime() < nextDate.getTime()) {
latestDate = new Date(nextDate.getTime()); latestDate = new Date(nextDate.getTime());
} }
} }
} }
// Just in case we end up in some sort of infinite loop. This shouldn't happen, and couldn't really Validate.notNull(latestDate);
// happen unless there were 10000 resources with the exact same update time down to the
// millisecond.
Date newLow; Date newLow;
if (latestDate == null) {
markJobAsDeleted(theJob);
return 0;
}
if (latestDate.getTime() == low.getTime()) { if (latestDate.getTime() == low.getTime()) {
ourLog.error("Final pass time for reindex JOB[{}] has same ending low value: {}", theJob.getId(), latestDate); if (count == PASS_SIZE) {
newLow = new Date(latestDate.getTime() + 1); // Just in case we end up in some sort of infinite loop. This shouldn't happen, and couldn't really
} else if (!haveMultipleDates) { // happen unless there were 10000 resources with the exact same update time down to the
// millisecond.
ourLog.error("Final pass time for reindex JOB[{}] has same ending low value: {}", theJob.getId(), latestDate);
}
newLow = new Date(latestDate.getTime() + 1); newLow = new Date(latestDate.getTime() + 1);
} else { } else {
newLow = latestDate; newLow = latestDate;
@ -323,10 +339,13 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
myTxTemplate.execute(t -> { myTxTemplate.execute(t -> {
myReindexJobDao.setThresholdLow(theJob.getId(), newLow); myReindexJobDao.setThresholdLow(theJob.getId(), newLow);
Integer existingCount = myReindexJobDao.getReindexCount(theJob.getId()).orElse(0);
int newCount = existingCount + counter.get();
myReindexJobDao.setReindexCount(theJob.getId(), newCount);
return null; return null;
}); });
ourLog.info("Completed pass of reindex JOB[{}] - Indexed {} resources in {} ({} / sec) - Have indexed until: {}", theJob.getId(), count, sw.toString(), sw.formatThroughput(count, TimeUnit.SECONDS), theJob.getThresholdLow()); ourLog.info("Completed pass of reindex JOB[{}] - Indexed {} resources in {} ({} / sec) - Have indexed until: {}", theJob.getId(), count, sw.toString(), sw.formatThroughput(count, TimeUnit.SECONDS), newLow);
return counter.get(); return counter.get();
} }
@ -450,6 +469,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
return e; return e;
} }
}); });
} catch (ResourceVersionConflictException e) { } catch (ResourceVersionConflictException e) {
/* /*
* We reindex in multiple threads, so it's technically possible that two threads try * We reindex in multiple threads, so it's technically possible that two threads try
@ -458,7 +478,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
* not get this error, so we'll let the other one fail and try * not get this error, so we'll let the other one fail and try
* again later. * again later.
*/ */
ourLog.info("Failed to reindex {} because of a version conflict. Leaving in unindexed state: {}", e.getMessage()); ourLog.info("Failed to reindex because of a version conflict. Leaving in unindexed state: {}", e.getMessage());
reindexFailure = null; reindexFailure = null;
} }

View File

@ -23,10 +23,15 @@ package ca.uhn.fhir.jpa.search.warm;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.MatchUrlService;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.ArrayList; import java.util.ArrayList;
@ -34,6 +39,7 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@Component
public class CacheWarmingSvcImpl implements ICacheWarmingSvc { public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
@Autowired @Autowired
@ -43,6 +49,8 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
private FhirContext myCtx; private FhirContext myCtx;
@Autowired @Autowired
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Autowired
private MatchUrlService myMatchUrlService;
@Override @Override
@Scheduled(fixedDelay = 1000) @Scheduled(fixedDelay = 1000)
@ -72,7 +80,7 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
RuntimeResourceDefinition resourceDef = parseUrlResourceType(myCtx, nextUrl); RuntimeResourceDefinition resourceDef = parseUrlResourceType(myCtx, nextUrl);
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceDef.getName()); IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceDef.getName());
String queryPart = parseWarmUrlParamPart(nextUrl); String queryPart = parseWarmUrlParamPart(nextUrl);
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(callingDao, myCtx, queryPart, resourceDef); SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(queryPart, resourceDef);
callingDao.search(responseCriteriaUrl); callingDao.search(responseCriteriaUrl);
} }

View File

@ -25,10 +25,12 @@ import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SearchParamPresent; import ca.uhn.fhir.jpa.entity.SearchParamPresent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
@Service
public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc { public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
@Autowired @Autowired

View File

@ -20,21 +20,19 @@ package ca.uhn.fhir.jpa.subscription;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Subscription; import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException; import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;
public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptionSubscriber { public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptionSubscriber {
private static final Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionDeliverySubscriber.class); private static final Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionDeliverySubscriber.class);
public BaseSubscriptionDeliverySubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) { public BaseSubscriptionDeliverySubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
super(theSubscriptionDao, theChannelType, theSubscriptionInterceptor); super(theChannelType, theSubscriptionInterceptor);
} }
@Override @Override
@ -59,21 +57,6 @@ public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptio
return; return;
} }
// Load the resource
IIdType payloadId = msg.getPayloadId(getContext());
Class type = getContext().getResourceDefinition(payloadId.getResourceType()).getImplementingClass();
IFhirResourceDao dao = getSubscriptionInterceptor().getDao(type);
IBaseResource loadedPayload;
try {
loadedPayload = dao.read(payloadId);
} catch (ResourceNotFoundException e) {
// This can happen if a last minute failure happens when saving a resource,
// eg a constraint causes the transaction to roll back on commit
ourLog.warn("Unable to find resource {} - Aborting delivery", payloadId.getValue());
return;
}
msg.setPayload(getContext(), loadedPayload);
handleMessage(msg); handleMessage(msg);
} catch (Exception e) { } catch (Exception e) {
String msg = "Failure handling subscription payload for subscription: " + subscriptionId; String msg = "Failure handling subscription payload for subscription: " + subscriptionId;

View File

@ -1,42 +1,27 @@
package ca.uhn.fhir.jpa.subscription; package ca.uhn.fhir.jpa.subscription;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.MatchUrlService;
import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.subscription.matcher.ISubscriptionMatcher; import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherCompositeInMemoryDatabase;
import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherDatabase; import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherDatabase;
import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -52,6 +37,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Subscription; import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@ -73,11 +59,32 @@ import javax.annotation.PreDestroy;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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%
*/
public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> extends ServerOperationInterceptorAdapter { public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> extends ServerOperationInterceptorAdapter {
static final String SUBSCRIPTION_STATUS = "Subscription.status"; static final String SUBSCRIPTION_STATUS = "Subscription.status";
static final String SUBSCRIPTION_TYPE = "Subscription.channel.type"; static final String SUBSCRIPTION_TYPE = "Subscription.channel.type";
private static final Integer MAX_SUBSCRIPTION_RESULTS = 1000; private static final Integer MAX_SUBSCRIPTION_RESULTS = 1000;
private static boolean ourForcePayloadEncodeAndDecodeForUnitTests;
private final Object myInitSubscriptionsLock = new Object(); private final Object myInitSubscriptionsLock = new Object();
private SubscribableChannel myProcessingChannel; private SubscribableChannel myProcessingChannel;
private Map<String, SubscribableChannel> myDeliveryChannel; private Map<String, SubscribableChannel> myDeliveryChannel;
@ -91,9 +98,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
private Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionInterceptor.class); private Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionInterceptor.class);
private ThreadPoolExecutor myDeliveryExecutor; private ThreadPoolExecutor myDeliveryExecutor;
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue; private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
private IFhirResourceDao<?> mySubscriptionDao;
@Autowired
private List<IFhirResourceDao<?>> myResourceDaos;
@Autowired @Autowired
private FhirContext myCtx; private FhirContext myCtx;
@Autowired(required = false) @Autowired(required = false)
@ -104,7 +108,16 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
@Autowired @Autowired
@Qualifier(BaseConfig.TASK_EXECUTOR_NAME) @Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
private AsyncTaskExecutor myAsyncTaskExecutor; private AsyncTaskExecutor myAsyncTaskExecutor;
private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> myResourceTypeToDao; @Autowired
private SubscriptionMatcherCompositeInMemoryDatabase mySubscriptionMatcherCompositeInMemoryDatabase;
@Autowired
private SubscriptionMatcherDatabase mySubscriptionMatcherDatabase;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private BeanFactory beanFactory;
@Autowired
private MatchUrlService myMatchUrlService;
private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1); private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1);
/** /**
@ -286,26 +299,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
public abstract Subscription.SubscriptionChannelType getChannelType(); public abstract Subscription.SubscriptionChannelType getChannelType();
// TODO KHS move out
@SuppressWarnings("unchecked")
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
if (myResourceTypeToDao == null) {
Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> theResourceTypeToDao = new HashMap<>();
for (IFhirResourceDao<?> next : myResourceDaos) {
theResourceTypeToDao.put(next.getResourceType(), next);
}
if (this instanceof IFhirResourceDao<?>) {
IFhirResourceDao<?> thiz = (IFhirResourceDao<?>) this;
theResourceTypeToDao.put(thiz.getResourceType(), thiz);
}
myResourceTypeToDao = theResourceTypeToDao;
}
return (IFhirResourceDao<R>) myResourceTypeToDao.get(theType);
}
protected MessageChannel getDeliveryChannel(CanonicalSubscription theSubscription) { protected MessageChannel getDeliveryChannel(CanonicalSubscription theSubscription) {
return mySubscribableChannel.get(theSubscription.getIdElement(myCtx).getIdPart()); return mySubscribableChannel.get(theSubscription.getIdElement(myCtx).getIdPart());
} }
@ -335,10 +328,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
myProcessingChannel = theProcessingChannel; myProcessingChannel = theProcessingChannel;
} }
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
public List<CanonicalSubscription> getRegisteredSubscriptions() { public List<CanonicalSubscription> getRegisteredSubscriptions() {
return new ArrayList<>(myIdToSubscription.values()); return new ArrayList<>(myIdToSubscription.values());
} }
@ -378,7 +367,8 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
RequestDetails req = new ServletSubRequestDetails(); RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true); req.setSubRequest(true);
IBundleProvider subscriptionBundleList = getSubscriptionDao().search(map, req); IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getResourceDao("Subscription");
IBundleProvider subscriptionBundleList = subscriptionDao.search(map, req);
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) { if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded."); ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
} }
@ -436,19 +426,14 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
protected void registerSubscriptionCheckingSubscriber() { protected void registerSubscriptionCheckingSubscriber() {
if (mySubscriptionCheckingSubscriber == null) { if (mySubscriptionCheckingSubscriber == null) {
ISubscriptionMatcher subscriptionMatcher = new SubscriptionMatcherDatabase(getSubscriptionDao(), this); mySubscriptionCheckingSubscriber = beanFactory.getBean(SubscriptionCheckingSubscriber.class, getChannelType(), this, mySubscriptionMatcherCompositeInMemoryDatabase);
mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this, subscriptionMatcher );
} }
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber); getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
} }
@Override @Override
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
ResourceModifiedMessage msg = new ResourceModifiedMessage(); submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
msg.setId(theResource.getIdElement());
msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.CREATE);
msg.setNewPayload(myCtx, theResource);
submitResourceModified(msg);
} }
@Override @Override
@ -465,10 +450,17 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
} }
void submitResourceModifiedForUpdate(IBaseResource theNewResource) { void submitResourceModifiedForUpdate(IBaseResource theNewResource) {
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
}
private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
ResourceModifiedMessage msg = new ResourceModifiedMessage(); ResourceModifiedMessage msg = new ResourceModifiedMessage();
msg.setId(theNewResource.getIdElement()); msg.setId(theNewResource.getIdElement());
msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.UPDATE); msg.setOperationType(theOperationType);
msg.setNewPayload(myCtx, theNewResource); msg.setNewPayload(myCtx, theNewResource);
if (ourForcePayloadEncodeAndDecodeForUnitTests) {
msg.clearPayloadDecoded();
}
submitResourceModified(msg); submitResourceModified(msg);
} }
@ -501,10 +493,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
myCtx = theCtx; myCtx = theCtx;
} }
public void setResourceDaos(List<IFhirResourceDao<?>> theResourceDaos) {
myResourceDaos = theResourceDaos;
}
@VisibleForTesting @VisibleForTesting
public void setTxManager(PlatformTransactionManager theTxManager) { public void setTxManager(PlatformTransactionManager theTxManager) {
myTxManager = theTxManager; myTxManager = theTxManager;
@ -512,15 +500,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
@PostConstruct @PostConstruct
public void start() { public void start() {
for (IFhirResourceDao<?> next : myResourceDaos) {
if (next.getResourceType() != null) {
if (myCtx.getResourceDefinition(next.getResourceType()).getName().equals("Subscription")) {
mySubscriptionDao = next;
}
}
}
Validate.notNull(mySubscriptionDao);
if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) { if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) {
Validate.notNull(myEventDefinitionDaoR4); Validate.notNull(myEventDefinitionDaoR4);
} }
@ -555,7 +534,8 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
} }
if (mySubscriptionActivatingSubscriber == null) { if (mySubscriptionActivatingSubscriber == null) {
mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager, myAsyncTaskExecutor); IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getResourceDao("Subscription");
mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(subscriptionDao, getChannelType(), this, myTxManager, myAsyncTaskExecutor);
} }
registerSubscriptionCheckingSubscriber(); registerSubscriptionCheckingSubscriber();
@ -619,5 +599,31 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
return myIdToSubscription.remove(subscriptionId); return myIdToSubscription.remove(subscriptionId);
} }
public IFhirResourceDao<?> getSubscriptionDao() {
return myDaoRegistry.getResourceDao("Subscription");
}
public IFhirResourceDao getDao(Class type) {
return myDaoRegistry.getResourceDao(type);
}
public void setResourceDaos(List<IFhirResourceDao> theResourceDaos) {
myDaoRegistry.setResourceDaos(theResourceDaos);
}
public void validateCriteria(final S theResource) {
CanonicalSubscription subscription = canonicalize(theResource);
String criteria = subscription.getCriteriaString();
try {
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myCtx, criteria);
myMatchUrlService.translateMatchUrl(criteria, resourceDef);
} catch (InvalidRequestException e) {
throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
}
}
@VisibleForTesting
public static void setForcePayloadEncodeAndDecodeForUnitTests(boolean theForcePayloadEncodeAndDecodeForUnitTests) {
ourForcePayloadEncodeAndDecodeForUnitTests = theForcePayloadEncodeAndDecodeForUnitTests;
}
} }

View File

@ -21,25 +21,40 @@ package ca.uhn.fhir.jpa.subscription;
*/ */
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import org.hl7.fhir.r4.model.Subscription; import org.hl7.fhir.r4.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessageHandler;
import javax.annotation.PostConstruct;
public abstract class BaseSubscriptionSubscriber implements MessageHandler { public abstract class BaseSubscriptionSubscriber implements MessageHandler {
private final IFhirResourceDao<?> mySubscriptionDao;
private final Subscription.SubscriptionChannelType myChannelType; private final Subscription.SubscriptionChannelType myChannelType;
private final BaseSubscriptionInterceptor mySubscriptionInterceptor; private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
@Autowired
DaoRegistry myDaoRegistry;
private IFhirResourceDao<?> mySubscriptionDao;
/** /**
* Constructor * Constructor
*/ */
public BaseSubscriptionSubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) { public BaseSubscriptionSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
mySubscriptionDao = theSubscriptionDao;
myChannelType = theChannelType; myChannelType = theChannelType;
mySubscriptionInterceptor = theSubscriptionInterceptor; mySubscriptionInterceptor = theSubscriptionInterceptor;
} }
@SuppressWarnings("unused") // Don't delete, used in Smile
public void setDaoRegistry(DaoRegistry theDaoRegistry) {
myDaoRegistry = theDaoRegistry;
}
@PostConstruct
public void setSubscriptionDao() {
mySubscriptionDao = myDaoRegistry.getResourceDao("Subscription");
}
public Subscription.SubscriptionChannelType getChannelType() { public Subscription.SubscriptionChannelType getChannelType() {
return myChannelType; return myChannelType;
} }
@ -71,7 +86,7 @@ public abstract class BaseSubscriptionSubscriber implements MessageHandler {
*/ */
static boolean subscriptionTypeApplies(String theSubscriptionChannelTypeCode, Subscription.SubscriptionChannelType theChannelType) { static boolean subscriptionTypeApplies(String theSubscriptionChannelTypeCode, Subscription.SubscriptionChannelType theChannelType) {
boolean subscriptionTypeApplies = false; boolean subscriptionTypeApplies = false;
if (theSubscriptionChannelTypeCode != null) { if (theSubscriptionChannelTypeCode != null) {
if (theChannelType.toCode().equals(theSubscriptionChannelTypeCode)) { if (theChannelType.toCode().equals(theSubscriptionChannelTypeCode)) {
subscriptionTypeApplies = true; subscriptionTypeApplies = true;
} }

Some files were not shown because too many files have changed in this diff Show More