Merge remote-tracking branch 'upstream/master' and fix tinder-test
conflict
This commit is contained in:
commit
689340368e
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -94,4 +94,9 @@ public class OkHttpRestfulRequest implements IHttpRequest {
|
||||||
return myRequestTypeEnum.name();
|
return myRequestTypeEnum.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeHeaders(String theHeaderName) {
|
||||||
|
myRequestBuilder.removeHeader(theHeaderName);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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&_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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue