Sync our fork with Hapi master. Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Matti Uusitalo 2018-12-10 13:20:27 +02:00
commit b76fe1e6be
531 changed files with 19310 additions and 7731 deletions

View File

@ -11,7 +11,7 @@ jdk:
- oraclejdk9
env:
global:
- MAVEN_OPTS="-Xmx1024m"
- MAVEN_OPTS="-Xmx10244M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1024M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC"
cache:
directories:

View File

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

View File

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

View File

@ -26,6 +26,8 @@ import java.util.Collection;
import java.util.List;
import java.util.Scanner;
// FIXME KHS
@Ignore
public class CdsExampleTests {
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu3();

View File

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

View File

@ -54,7 +54,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
/**
* Configure FHIR properties around the the JPA server via this bean
*/
@Bean()
@Bean
public DaoConfig daoConfig() {
return FhirServerConfigCommon.getDaoConfig();
}
@ -71,7 +71,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
}
@Override
@Bean()
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
return FhirServerConfigCommon.getEntityManagerFactory(env, dataSource(), fhirContextDstu3());
}
@ -99,7 +99,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
return interceptor;
}
@Bean()
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return FhirServerConfigCommon.getTransactionManager(entityManagerFactory);
}

View File

@ -57,7 +57,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
* Configure FHIR properties around the the JPA server via this bean
*/
@SuppressWarnings("deprecation")
@Bean()
@Bean
public DaoConfig daoConfig() {
return FhirServerConfigCommon.getDaoConfig();
}
@ -74,7 +74,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
}
@Override
@Bean()
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
return FhirServerConfigCommon.getEntityManagerFactory(env, dataSource(), fhirContextDstu2());
}
@ -103,7 +103,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
return interceptor;
}
@Bean()
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return FhirServerConfigCommon.getTransactionManager(entityManagerFactory);
}

View File

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

View File

@ -37,7 +37,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
/**
* Configure FHIR properties around the the JPA server via this bean
*/
@Bean()
@Bean
public DaoConfig daoConfig() {
DaoConfig retVal = new DaoConfig();
retVal.setSubscriptionEnabled(true);
@ -64,13 +64,11 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
}
@Override
@Bean()
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean();
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
retVal.setDataSource(dataSource());
retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity");
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
retVal.setJpaProperties(jpaProperties());
return retVal;
}
@ -122,7 +120,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
return retVal;
}
@Bean()
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -73,12 +73,12 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeElementCompositeDefinition.class);
private Map<String, Integer> forcedOrder = null;
private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<BaseRuntimeChildDefinition>();
private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<>();
private List<BaseRuntimeChildDefinition> myChildrenAndExtensions;
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions;
private FhirContext myContext;
private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
private List<ScannedField> myScannedFields = new ArrayList<BaseRuntimeElementCompositeDefinition.ScannedField>();
private final FhirContext myContext;
private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<>();
private List<ScannedField> myScannedFields = new ArrayList<>();
private volatile boolean mySealed;
@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
*/
Class<? extends IBase> current = theImplementingClass;
LinkedList<Class<? extends IBase>> classes = new LinkedList<Class<? extends IBase>>();
LinkedList<Class<? extends IBase>> classes = new LinkedList<>();
do {
if (forcedOrder == null) {
ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
if (childOrder != null) {
forcedOrder = new HashMap<String, Integer>();
forcedOrder = new HashMap<>();
for (int i = 0; i < childOrder.names().length; i++) {
String nextName = childOrder.names()[i];
if (nextName.endsWith("[x]")) {
@ -115,7 +115,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
}
} while (current != null);
Set<Field> fields = new HashSet<Field>();
Set<Field> fields = new HashSet<>();
for (Class<? extends IBase> nextClass : classes) {
int fieldIndexInClass = 0;
for (Field next : nextClass.getDeclaredFields()) {
@ -192,9 +192,9 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
}
private void scanCompositeElementForChildren() {
Set<String> elementNames = new HashSet<String>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
Set<String> elementNames = new HashSet<>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<>();
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
* for forced order. Those elements come first.
*/
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<>();
int unknownCount = 0;
for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
if (!forcedOrder.containsKey(nextEntry.getElementName())) {
@ -220,7 +220,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
orderToElementDef = newOrderToExtensionDef;
}
TreeSet<Integer> orders = new TreeSet<Integer>();
TreeSet<Integer> orders = new TreeSet<>();
orders.addAll(orderToElementDef.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
*/
if (order == Child.ORDER_UNKNOWN) {
order = Integer.valueOf(0);
order = 0;
while (orderMap.containsKey(order)) {
order++;
}
@ -386,7 +386,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
/*
* 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()) {
if (IBaseReference.class.isAssignableFrom(nextType)) {
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);
}
myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
myNameToChild = new HashMap<>();
for (BaseRuntimeChildDefinition next : myChildren) {
if (next instanceof RuntimeChildChoiceDefinition) {
String key = ((RuntimeChildChoiceDefinition) next).getElementName()+"[x]";
String key = next.getElementName()+"[x]";
myNameToChild.put(key, next);
}
for (String nextName : next.getValidChildNames()) {
@ -486,7 +486,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
myChildren = Collections.unmodifiableList(myChildren);
myNameToChild = Collections.unmodifiableMap(myNameToChild);
List<BaseRuntimeChildDefinition> children = new ArrayList<BaseRuntimeChildDefinition>();
List<BaseRuntimeChildDefinition> children = new ArrayList<>();
children.addAll(myChildren);
/*
@ -554,11 +554,11 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
private static class ScannedField {
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 Field myField;
private boolean myFirstFieldInNewClass;
public ScannedField(Field theField, Class<?> theClass, boolean theFirstFieldInNewClass) {
ScannedField(Field theField, Class<?> theClass, boolean theFirstFieldInNewClass) {
myField = theField;
myFirstFieldInNewClass = theFirstFieldInNewClass;
@ -574,10 +574,8 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
myChildAnnotation = childAnnotation;
myElementType = ModelScanner.determineElementType(theField);
for (Class<? extends IBase> nextChoiceType : childAnnotation.type()) {
myChoiceTypes.add(nextChoiceType);
}
Collections.addAll(myChoiceTypes, childAnnotation.type());
}
public Child getChildAnnotation() {

View File

@ -19,17 +19,6 @@ package ca.uhn.fhir.context;
* limitations under the License.
* #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.model.api.*;
@ -38,6 +27,19 @@ import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
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 {
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;
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;
myVersion = theVersion;
Set<Class<? extends IBase>> toScan;
@ -67,32 +69,6 @@ class ModelScanner {
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() {
return myClassToElementDefinitions;
}
@ -137,11 +113,7 @@ class ModelScanner {
for (Class<? extends IBase> nextClass : typesToScan) {
scan(nextClass);
}
for (Iterator<Class<? extends IBase>> iter = myScanAlso.iterator(); iter.hasNext();) {
if (myClassToElementDefinitions.containsKey(iter.next())) {
iter.remove();
}
}
myScanAlso.removeIf(theClass -> myClassToElementDefinitions.containsKey(theClass));
typesToScan.clear();
typesToScan.addAll(myScanAlso);
myScanAlso.clear();
@ -152,7 +124,7 @@ class ModelScanner {
continue;
}
BaseRuntimeElementDefinition<?> next = nextEntry.getValue();
boolean deferredSeal = false;
if (myContext.getPerformanceOptions().contains(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING)) {
if (next instanceof BaseRuntimeElementCompositeDefinition) {
@ -177,16 +149,6 @@ class ModelScanner {
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 {
BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
if (existingDef != null) {
@ -197,7 +159,7 @@ class ModelScanner {
if (resourceDefinition != null) {
if (!IBaseResource.class.isAssignableFrom(theClass)) {
throw new ConfigurationException(
"Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName());
"Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName());
}
@SuppressWarnings("unchecked")
Class<? extends IBaseResource> resClass = (Class<? extends IBaseResource>) theClass;
@ -212,11 +174,11 @@ class ModelScanner {
Class<? extends ICompositeType> resClass = (Class<? extends ICompositeType>) theClass;
scanCompositeDatatype(resClass, datatypeDefinition);
} else if (IPrimitiveType.class.isAssignableFrom(theClass)) {
@SuppressWarnings({ "unchecked" })
@SuppressWarnings({"unchecked"})
Class<? extends IPrimitiveType<?>> resClass = (Class<? extends IPrimitiveType<?>>) theClass;
scanPrimitiveDatatype(resClass, datatypeDefinition);
}
}
return;
}
@ -227,13 +189,13 @@ class ModelScanner {
scanBlock(theClass);
} else {
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
//Redundant checking && datatypeDefinition == null && resourceDefinition == null
) {
) {
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());
}
// 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);
blockDef.populateScanAlso(myScanAlso);
myClassToElementDefinitions.put(theClass, blockDef);
}
@ -272,14 +243,6 @@ class ModelScanner {
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) {
ourLog.debug("Scanning resource class: {}", theClass.getName());
@ -333,7 +296,7 @@ class ModelScanner {
}
if (isBlank(resourceName)) {
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name(): " + theClass.getCanonicalName()
+ " - This is only allowed for types that extend other resource types ");
+ " - This is only allowed for types that extend other resource types ");
}
}
@ -345,12 +308,12 @@ class ModelScanner {
primaryNameProvider = false;
}
}
String resourceId = resourceDefinition.id();
if (!isBlank(resourceId)) {
if (myIdToResourceDefinition.containsKey(resourceId)) {
throw new ConfigurationException("The following resource types have the same ID of '" + resourceId + "' - " + theClass.getCanonicalName() + " and "
+ myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName());
+ myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName());
}
}
@ -372,7 +335,7 @@ class ModelScanner {
* sure that this type gets scanned as well
*/
resourceDef.populateScanAlso(myScanAlso);
return resourceName;
}
@ -393,7 +356,7 @@ class ModelScanner {
}
nextClass = nextClass.getSuperclass();
} while (nextClass.equals(Object.class) == false);
/*
* Now scan the fields for search params
*/
@ -404,8 +367,8 @@ class ModelScanner {
if (paramType == null) {
throw new ConfigurationException("Search param " + searchParam.name() + " has an invalid type: " + searchParam.type());
}
Set<String> providesMembershipInCompartments = null;
providesMembershipInCompartments = new HashSet<String>();
Set<String> providesMembershipInCompartments;
providesMembershipInCompartments = new HashSet<>();
for (Compartment next : searchParam.providesMembershipIn()) {
if (paramType != RestSearchParameterTypeEnum.REFERENCE) {
StringBuilder b = new StringBuilder();
@ -420,14 +383,15 @@ class ModelScanner {
}
providesMembershipInCompartments.add(next.name());
}
if (paramType == RestSearchParameterTypeEnum.COMPOSITE) {
compositeFields.put(nextField, searchParam);
continue;
}
RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType, providesMembershipInCompartments, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE);
Collection<String> base = Collections.singletonList(theResourceDef.getName());
RuntimeSearchParam param = new RuntimeSearchParam(null, null, searchParam.name(), searchParam.description(), searchParam.path(), paramType, null, providesMembershipInCompartments, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE, base);
theResourceDef.addSearchParam(param);
nameToParam.put(param.getName(), param);
}
@ -441,7 +405,7 @@ class ModelScanner {
RuntimeSearchParam param = nameToParam.get(nextName);
if (param == null) {
ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}",
new Object[] { theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet() });
new Object[]{theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()});
continue;
}
compositeOf.add(param);
@ -454,17 +418,59 @@ class ModelScanner {
private Set<String> toTargetList(Class<? extends IBaseResource>[] theTarget) {
HashSet<String> retVal = new HashSet<String>();
for (Class<? extends IBaseResource> nextType : theTarget) {
ResourceDef resourceDef = nextType.getAnnotation(ResourceDef.class);
if (resourceDef != null) {
retVal.add(resourceDef.name());
}
}
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) {
Class<?> type;
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();

View File

@ -49,9 +49,8 @@ public class RuntimeChildResourceDefinition extends BaseRuntimeDeclaredChildDefi
myResourceTypes = theResourceTypes;
if (theResourceTypes == null || theResourceTypes.isEmpty()) {
myResourceTypes = new ArrayList<Class<? extends IBaseResource>>();
myResourceTypes = new ArrayList<>();
myResourceTypes.add(IBaseResource.class);
// throw new ConfigurationException("Field '" + theField.getName() + "' on type '" + theField.getDeclaringClass().getCanonicalName() + "' has no resource types noted");
}
}

View File

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

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.fluentpath;
*/
import java.util.List;
import java.util.Optional;
import org.hl7.fhir.instance.model.api.IBase;
@ -36,6 +37,15 @@ public interface IFluentPath {
*/
<T extends IBase> List<T> evaluate(IBase theInput, String thePath, Class<T> theReturnType);
/**
* Apply the given FluentPath expression against the given input and return
* the first match (if any)
*
* @param theInput The input object (generally a resource or datatype)
* @param thePath The fluent path expression
* @param theReturnType The type to return (in order to avoid casting)
*/
<T extends IBase> Optional<T> evaluateFirst(IBase theInput, String thePath, Class<T> theReturnType);
}

View File

@ -60,7 +60,7 @@ public interface IQueryParameterType extends Serializable {
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();

View File

@ -34,6 +34,7 @@ import org.thymeleaf.cache.ICacheEntryValidity;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AttributeName;
import org.thymeleaf.messageresolver.IMessageResolver;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
@ -65,6 +66,8 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private HashMap<String, String> myNameToNarrativeTemplate;
private TemplateEngine myProfileTemplateEngine;
private IMessageResolver resolver;
/**
* Constructor
*/
@ -166,11 +169,21 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
};
myProfileTemplateEngine.setDialect(dialect);
if (this.resolver != null) {
myProfileTemplateEngine.setMessageResolver(this.resolver);
}
}
myInitialized = true;
}
public void setMessageResolver(IMessageResolver resolver) {
this.resolver = resolver;
if (myProfileTemplateEngine != null && resolver != null) {
myProfileTemplateEngine.setMessageResolver(resolver);
}
}
/**
* If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative
* before it is returned.

View File

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

View File

@ -19,14 +19,23 @@ package ca.uhn.fhir.parser;
* limitations under the License.
* #L%
*/
import java.io.*;
import java.util.*;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.model.api.IResource;
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
@ -127,6 +136,20 @@ public interface IParser {
*/
<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
*
@ -153,6 +176,19 @@ public interface IParser {
*/
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
*

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.annotation;
* 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.

View File

@ -42,8 +42,14 @@ public class Constants {
*/
public static final Set<String> CORS_ALLWED_METHODS;
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_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_HTML = "text/html";
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_LC = HEADER_CONTENT_LOCATION.toLowerCase();
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_CORS_ALLOW_METHODS = "Access-Control-Allow-Methods";
public static final String HEADER_CORS_ALLOW_ORIGIN = "Access-Control-Allow-Origin";

View File

@ -20,11 +20,13 @@ package ca.uhn.fhir.rest.api;
* #L%
*/
import ca.uhn.fhir.util.CoverageIgnore;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
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 {
@ -32,6 +34,7 @@ public class MethodOutcome {
private IIdType myId;
private IBaseOperationOutcome myOperationOutcome;
private IBaseResource myResource;
private Map<String, List<String>> myResponseHeaders;
/**
* Constructor
@ -42,13 +45,10 @@ public class MethodOutcome {
/**
* Constructor
*
* @param theId
* 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 theId 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.
*/
@CoverageIgnore
public MethodOutcome(IIdType theId, Boolean theCreated) {
@ -58,12 +58,9 @@ public class MethodOutcome {
/**
* Constructor
*
* @param theId
* The ID of the created/updated resource
*
* @param theBaseOperationOutcome
* The operation outcome to return with the response (or null for none)
*
* @param theId The ID of the created/updated resource
* @param theBaseOperationOutcome The operation outcome to return with the response (or null for none)
*/
public MethodOutcome(IIdType theId, IBaseOperationOutcome theBaseOperationOutcome) {
myId = theId;
@ -72,16 +69,11 @@ public class MethodOutcome {
/**
* Constructor
*
* @param theId
* 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
* 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 theId 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
* 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) {
myId = theId;
@ -91,9 +83,8 @@ public class MethodOutcome {
/**
* Constructor
*
* @param theId
* The ID of the created/updated resource
*
* @param theId The ID of the created/updated resource
*/
public MethodOutcome(IIdType theId) {
myId = theId;
@ -101,9 +92,8 @@ public class MethodOutcome {
/**
* Constructor
*
* @param theOperationOutcome
* The operation outcome resource to return
*
* @param theOperationOutcome The operation outcome resource to return
*/
public MethodOutcome(IBaseOperationOutcome theOperationOutcome) {
myOperationOutcome = theOperationOutcome;
@ -117,19 +107,54 @@ public class MethodOutcome {
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() {
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.
*
*
* @return This method <b>will return null</b>, unlike many methods in the API.
*/
public IBaseOperationOutcome getOperationOutcome() {
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
* "Prefer: return=representation") this field will be populated with the
@ -139,50 +164,15 @@ public class MethodOutcome {
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
* where a resource body is being created/updated. E.g. for an update method, this field could be populated with
* the resource after the update is applied, with the new version ID, lastUpdate time, etc.
* the resource after the update is applied, with the new version ID, lastUpdate time, etc.
* <p>
* 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.
* </p>
* </p>
*
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public MethodOutcome setResource(IBaseResource theResource) {
@ -190,4 +180,23 @@ public class MethodOutcome {
return this;
}
/**
* Gets the headers for the HTTP response
*/
public Map<String, List<String>> getResponseHeaders() {
return myResponseHeaders;
}
/**
* Sets the headers for the HTTP response
*/
public void setResponseHeaders(Map<String, List<String>> theResponseHeaders) {
myResponseHeaders = theResponseHeaders;
}
public void setCreatedUsingStatusCode(int theResponseStatusCode) {
if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
setCreated(true);
}
}
}

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.jpa.util;
package ca.uhn.fhir.rest.api;
/*-
* #%L
* HAPI FHIR JPA Server
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@ -20,15 +20,15 @@ package ca.uhn.fhir.jpa.util;
* #L%
*/
public interface IReindexController {
public enum RequestFormatParamStyleEnum {
/**
* Do not include a _format parameter on requests
*/
NONE,
/**
* This method is called automatically by the scheduler
* "xml" or "json"
*/
void performReindexingPass();
SHORT
/**
* This method requests that the reindex process happen as soon as possible
*/
void requestReindex();
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.api;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* 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.HashMap;
import java.util.Map;

View File

@ -28,16 +28,18 @@ import java.util.Map;
* Http Request. Allows addition of headers and execution of the request.
*/
public interface IHttpRequest {
/**
* Add a header to the request
* @param theName the header name
*
* @param theName the header name
* @param theValue the header value
*/
void addHeader(String theName, String theValue);
/**
* Execute the request
*
* @return the response
*/
IHttpResponse execute() throws IOException;
@ -50,7 +52,8 @@ public interface IHttpRequest {
/**
* 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.
*/
String getRequestBodyFromStream() throws IOException;
@ -59,10 +62,16 @@ public interface IHttpRequest {
* Return the request URI, or null
*/
String getUri();
/**
* Return the HTTP verb (e.g. "GET")
*/
String getHttpVerbName();
/**
* Remove any headers matching the given name
*
* @param theHeaderName The header name, e.g. "Accept" (must not be null or blank)
*/
void removeHeaders(String theHeaderName);
}

View File

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

View File

@ -1,5 +1,11 @@
package ca.uhn.fhir.rest.client.api;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
/*
@ -22,23 +28,15 @@ import java.util.List;
* #L%
*/
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
public interface IRestfulClient {
/**
* Retrieve the contents at the given URL and parse them as a resource. This
* method could be used as a low level implementation of a read/vread/search
* operation.
*
* @param theResourceType
* The resource type to parse
* @param theUrl
* The URL to load
*
* @param theResourceType The resource type to parse
* @param theUrl The URL to load
* @return The parsed resource
*/
<T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl);
@ -49,6 +47,17 @@ public interface IRestfulClient {
*/
EncodingEnum getEncoding();
/**
* Specifies that the client should use the given encoding to do its
* queries. This means that the client will append the "_format" param
* to GET methods (read/search/etc), and will add an appropriate header for
* write methods.
*
* @param theEncoding The encoding to use in the request, or <code>null</code> not specify
* an encoding (which generally implies the use of XML). The default is <code>null</code>.
*/
void setEncoding(EncodingEnum theEncoding);
/**
* Returns the FHIR context associated with this client
*/
@ -76,25 +85,12 @@ public interface IRestfulClient {
*/
void registerInterceptor(IClientInterceptor theInterceptor);
/**
* Specifies that the client should use the given encoding to do its
* queries. This means that the client will append the "_format" param
* to GET methods (read/search/etc), and will add an appropriate header for
* write methods.
*
* @param theEncoding
* The encoding to use in the request, or <code>null</code> not specify
* an encoding (which generally implies the use of XML). The default is <code>null</code>.
*/
void setEncoding(EncodingEnum theEncoding);
/**
* Specifies that the client should request that the server respond with "pretty printing"
* enabled. Note that this is a non-standard parameter, not all servers will
* support it.
*
* @param thePrettyPrint
* The pretty print flag to use in the request (default is <code>false</code>)
*
* @param thePrettyPrint The pretty print flag to use in the request (default is <code>false</code>)
*/
void setPrettyPrint(Boolean thePrettyPrint);
@ -109,4 +105,8 @@ public interface IRestfulClient {
*/
void unregisterInterceptor(IClientInterceptor theInterceptor);
/**
* Configures what style of _format parameter should be used in requests
*/
void setFormatParamStyle(RequestFormatParamStyleEnum theRequestFormatParamStyle);
}

View File

@ -19,15 +19,18 @@ package ca.uhn.fhir.rest.client.exceptions;
* limitations under the License.
* #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.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
public class NonFhirResponseException extends BaseServerResponseException {
@ -36,24 +39,30 @@ public class NonFhirResponseException extends BaseServerResponseException {
/**
* Constructor
*
* @param theMessage
* The message
* @param theResponseText
* @param theStatusCode
* @param theResponseReader
* @param theContentType
*
* @param theMessage The message
* @param theStatusCode The HTTP status code
*/
NonFhirResponseException(int theStatusCode, String 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) {
String responseBody = "";
try {
responseBody = IOUtils.toString(theReader);
} catch (IOException e) {
IOUtils.closeQuietly(theReader);
// ignore
} finally {
try {
theReader.close();
} catch (IOException theE) {
// ignore
}
}
NonFhirResponseException retVal;

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -28,12 +29,12 @@ 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
* debugging, but is generally not desirable in a production situation.
*
*
* @deprecated Use the client logging interceptor to log requests and responses instead. See <a href="http://jamesagnew.github.io/hapi-fhir/doc_rest_client.html#req_resp_logging">here</a> for more information.
*/
@Deprecated
@ -46,16 +47,45 @@ public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> {
T cacheControl(CacheControlDirective theCacheControlDirective);
/**
* Request that the server return subsetted resources, containing only the elements specified in the given parameters.
* Request that the server return subsetted resources, containing only the elements specified in the given parameters.
* For example: <code>subsetElements("name", "identifier")</code> requests that the server only return
* the "name" and "identifier" fields in the returned resource, and omit any others.
* the "name" and "identifier" fields in the returned resource, and omit any others.
*/
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);
/**
* 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();
/**
* 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();
/**
@ -84,11 +114,33 @@ public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> {
*/
T preferResponseTypes(List<Class<? extends IBaseResource>> theTypes);
/**
* Request pretty-printed response via the <code>_pretty</code> parameter
*/
T prettyPrint();
/**
* Request that the server modify the response using the <code>_summary</code> param
* Request that the server modify the response using the <code>_summary</code> param
*/
T summaryMode(SummaryEnum theSummary);
/**
* Specifies a custom <code>Accept</code> header that should be supplied with the
* request.
* <p>
* Note that this method overrides any encoding preferences specified with
* {@link #encodedJson()} or {@link #encodedXml()}. It is generally easier to
* just use those methods if you simply want to request a specific FHIR encoding.
* </p>
*
* @param theHeaderValue The header value, e.g. "application/fhir+json". Constants such
* as {@link ca.uhn.fhir.rest.api.Constants#CT_FHIR_XML_NEW} and
* {@link ca.uhn.fhir.rest.api.Constants#CT_FHIR_JSON_NEW} may
* be useful. If set to <code>null</code> or an empty string, the
* default Accept header will be used.
* @see #encoded(EncodingEnum)
* @see #encodedJson()
* @see #encodedXml()
*/
T accept(String theHeaderValue);
}

View File

@ -34,7 +34,7 @@ public interface IOperationUntyped {
* @param theParameters The parameters to use as input. May also be <code>null</code> if the operation
* does not require any input parameters.
*/
<T extends IBaseParameters> IOperationUntypedWithInput<T> withParameters(T theParameters);
<T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withParameters(T theParameters);
/**
* The operation does not require any input parameters

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.gclient;
* #L%
*/
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
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);
/**
* Request that the method chain returns a {@link MethodOutcome} object. This object
* will contain details
*/
IOperationUntypedWithInput<MethodOutcome> returnMethodOutcome();
}

View File

@ -38,6 +38,8 @@ public interface IOperationUntypedWithInputAndPartialOutput<T extends IBaseParam
IOperationUntypedWithInputAndPartialOutput<T> andParameter(String theName, IBase theValue);
/**
* Adds a URL parameter to the request.
*
* Use chained method calls to construct a Parameters input. This form is a convenience
* in order to allow simple method chaining to be used to build up a parameters
* resource for the input of an operation without needing to manually construct one.

View File

@ -1,9 +1,11 @@
package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.Collection;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.IdDt;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
@ -38,17 +40,43 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
public String getParamName() {
return myName;
}
/**
* Include a chained search. For example:
* <pre>
* Bundle resp = ourClient
* .search()
* .forResource(QuestionnaireResponse.class)
* .where(QuestionnaireResponse.SUBJECT.hasChainedProperty(Patient.FAMILY.matches().value("SMITH")))
* .returnBundle(Bundle.class)
* .execute();
* </pre>
*/
public ICriterion<ReferenceClientParam> hasChainedProperty(ICriterion<?> theCriterion) {
return new ReferenceChainCriterion(getParamName(), theCriterion);
}
/**
* Include a chained search with a resource type. For example:
* <pre>
* Bundle resp = ourClient
* .search()
* .forResource(QuestionnaireResponse.class)
* .where(QuestionnaireResponse.SUBJECT.hasChainedProperty("Patient", Patient.FAMILY.matches().value("SMITH")))
* .returnBundle(Bundle.class)
* .execute();
* </pre>
*/
public ICriterion<ReferenceClientParam> hasChainedProperty(String theResourceType, ICriterion<?> theCriterion) {
return new ReferenceChainCriterion(getParamName(), theResourceType, theCriterion);
}
/**
* Match the referenced resource if the resource has the given ID (this can be
* the logical ID or the absolute URL of the resource)
*/
public ICriterion<ReferenceClientParam> hasId(IdDt theId) {
return new StringCriterion<ReferenceClientParam>(getParamName(), theId.getValue());
public ICriterion<ReferenceClientParam> hasId(IIdType theId) {
return new StringCriterion<>(getParamName(), theId.getValue());
}
/**
@ -56,7 +84,7 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
* the logical ID or the absolute URL of the resource)
*/
public ICriterion<ReferenceClientParam> hasId(String theId) {
return new StringCriterion<ReferenceClientParam>(getParamName(), theId);
return new StringCriterion<>(getParamName(), theId);
}
/**
@ -67,22 +95,28 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
* with the same parameter.
*/
public ICriterion<ReferenceClientParam> hasAnyOfIds(Collection<String> theIds) {
return new StringCriterion<ReferenceClientParam>(getParamName(), theIds);
return new StringCriterion<>(getParamName(), theIds);
}
private static class ReferenceChainCriterion implements ICriterion<ReferenceClientParam>, ICriterionInternal {
private final String myResourceTypeQualifier;
private String myParamName;
private ICriterionInternal myWrappedCriterion;
public ReferenceChainCriterion(String theParamName, ICriterion<?> theWrappedCriterion) {
ReferenceChainCriterion(String theParamName, ICriterion<?> theWrappedCriterion) {
this(theParamName, null, theWrappedCriterion);
}
ReferenceChainCriterion(String theParamName, String theResourceType, ICriterion<?> theWrappedCriterion) {
myParamName = theParamName;
myResourceTypeQualifier = isNotBlank(theResourceType) ? ":" + theResourceType : "";
myWrappedCriterion = (ICriterionInternal) theWrappedCriterion;
}
@Override
public String getParameterName() {
return myParamName + "." + myWrappedCriterion.getParameterName();
return myParamName + myResourceTypeQualifier + "." + myWrappedCriterion.getParameterName();
}
@Override

View File

@ -255,7 +255,7 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
}
public Date getLowerBoundAsInstant() {
if (myLowerBound == null) {
if (myLowerBound == null || myLowerBound.getValue() == null) {
return null;
}
Date retVal = myLowerBound.getValue();
@ -310,7 +310,7 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
}
public Date getUpperBoundAsInstant() {
if (myUpperBound == null) {
if (myUpperBound == null || myUpperBound.getValue() == null) {
return null;
}

View File

@ -100,7 +100,11 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ {
@Override
String doGetValueAsQueryToken(FhirContext theContext) {
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());
}

View File

@ -28,9 +28,9 @@ import static org.apache.commons.lang3.StringUtils.*;
* 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.
@ -96,34 +96,36 @@ public class FhirTerser {
/**
* Clones all values from a source object into the equivalent fields in a target object
* @param theSource The source object (must not be null)
* @param theTarget The target object to copy values into (must not be null)
*
* @param theSource The source object (must not be null)
* @param theTarget The target object to copy values into (must not be null)
* @param theIgnoreMissingFields The ignore fields in the target which do not exist (if false, an exception will be thrown if the target is unable to accept a value from the source)
* @return Returns the target (which will be the same object that was passed into theTarget) for easy chaining
*/
public void cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
public IBase cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
Validate.notNull(theSource, "theSource must not be null");
Validate.notNull(theTarget, "theTarget must not be null");
if (theSource instanceof IPrimitiveType<?>) {
if (theTarget instanceof IPrimitiveType<?>) {
((IPrimitiveType<?>)theTarget).setValueAsString(((IPrimitiveType<?>)theSource).getValueAsString());
return;
((IPrimitiveType<?>) theTarget).setValueAsString(((IPrimitiveType<?>) theSource).getValueAsString());
return theSource;
}
if (theIgnoreMissingFields) {
return;
return theSource;
}
throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName());
}
BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass());
BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass());
BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass());
List<BaseRuntimeChildDefinition> children = sourceDef.getChildren();
if (sourceDef instanceof RuntimeExtensionDtDefinition) {
children = ((RuntimeExtensionDtDefinition)sourceDef).getChildrenIncludingUrl();
children = ((RuntimeExtensionDtDefinition) sourceDef).getChildrenIncludingUrl();
}
for (BaseRuntimeChildDefinition nextChild : children) {
for (BaseRuntimeChildDefinition nextChild : children)
for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) {
String elementName = nextChild.getChildNameByDatatype(nextValue.getClass());
BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName);
@ -133,14 +135,15 @@ public class FhirTerser {
}
throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + elementName);
}
BaseRuntimeElementDefinition<?> childDef = targetChild.getChildByName(elementName);
IBase target = childDef.newInstance();
BaseRuntimeElementDefinition<?> element = myContext.getElementDefinition(nextValue.getClass());
IBase target = element.newInstance();
targetChild.getMutator().addValue(theTarget, target);
cloneInto(nextValue, target, theIgnoreMissingFields);
}
}
return theTarget;
}
/**
@ -153,11 +156,9 @@ public class FhirTerser {
* Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
* {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
* </p>
*
* @param theResource
* The resource instance to search. Must not be null.
* @param theType
* The type to search for. Must not be null.
*
* @param theResource The resource instance to search. Must not be null.
* @param theType The type to search for. Must not be null.
* @return Returns a list of all matching elements
*/
public <T extends IBase> List<T> getAllPopulatedChildElementsOfType(IBaseResource theResource, final Class<T> theType) {
@ -274,7 +275,7 @@ public class FhirTerser {
.collect(Collectors.toList());
if (theAddExtension
&& (!(theCurrentObj instanceof IBaseExtension) || (extensionDts.isEmpty() && theSubList.size() == 1))) {
&& (!(theCurrentObj instanceof IBaseExtension) || (extensionDts.isEmpty() && theSubList.size() == 1))) {
extensionDts.add(createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
}
@ -286,7 +287,7 @@ public class FhirTerser {
extensionDts = ((IBaseExtension) theCurrentObj).getExtension();
if (theAddExtension
&& (extensionDts.isEmpty() && theSubList.size() == 1)) {
&& (extensionDts.isEmpty() && theSubList.size() == 1)) {
extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
}
@ -311,7 +312,7 @@ public class FhirTerser {
.collect(Collectors.toList());
if (theAddExtension
&& (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) {
&& (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) {
extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl));
}
@ -396,7 +397,7 @@ public class FhirTerser {
.collect(Collectors.toList());
if (theAddExtension
&& (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) {
&& (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) {
extensions.add(createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl));
}
@ -478,7 +479,7 @@ public class FhirTerser {
* type {@link Object}.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param thePath The path for the element to be accessed.
* @return A list of values of type {@link Object}.
*/
public List<Object> getValues(IBaseResource theResource, String thePath) {
@ -492,8 +493,8 @@ public class FhirTerser {
* type {@link Object}.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param thePath The path for the element to be accessed.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @return A list of values of type {@link Object}.
*/
public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate) {
@ -506,9 +507,9 @@ public class FhirTerser {
* Returns values stored in an element identified by its path. The list of values is of
* type {@link Object}.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
* @return A list of values of type {@link Object}.
*/
@ -522,10 +523,10 @@ public class FhirTerser {
* Returns values stored in an element identified by its path. The list of values is of
* type <code>theWantedClass</code>.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theWantedClass The desired class to be returned in a list.
* @param <T> Type declared by <code>theWantedClass</code>
* @param <T> Type declared by <code>theWantedClass</code>
* @return A list of values of type <code>theWantedClass</code>.
*/
public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) {
@ -538,11 +539,11 @@ public class FhirTerser {
* Returns values stored in an element identified by its path. The list of values is of
* type <code>theWantedClass</code>.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theWantedClass The desired class to be returned in a list.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param <T> Type declared by <code>theWantedClass</code>
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param <T> Type declared by <code>theWantedClass</code>
* @return A list of values of type <code>theWantedClass</code>.
*/
public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate) {
@ -555,12 +556,12 @@ public class FhirTerser {
* Returns values stored in an element identified by its path. The list of values is of
* type <code>theWantedClass</code>.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theWantedClass The desired class to be returned in a list.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theWantedClass The desired class to be returned in a list.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
* @param <T> Type declared by <code>theWantedClass</code>
* @param <T> Type declared by <code>theWantedClass</code>
* @return A list of values of type <code>theWantedClass</code>.
*/
public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
@ -605,10 +606,10 @@ public class FhirTerser {
/**
* Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
* belonging to resource <code>theTarget</code>
*
*
* @param theCompartmentName The name of the compartment
* @param theSource The potential member of the compartment
* @param theTarget The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException}
* @param theSource The potential member of the compartment
* @param theTarget The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException}
* @return <code>true</code> if <code>theSource</code> is in the compartment
* @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
*/
@ -618,16 +619,16 @@ public class FhirTerser {
Validate.notNull(theTarget, "theTarget must not be null");
Validate.notBlank(defaultString(theTarget.getResourceType()), "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)");
Validate.notBlank(defaultString(theTarget.getIdPart()), "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)");
String wantRef = theTarget.toUnqualifiedVersionless().getValue();
RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource);
if (theSource.getIdElement().hasIdPart()) {
if (wantRef.equals(sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) {
return true;
}
}
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
for (RuntimeSearchParam nextParam : params) {
for (String nextPath : nextParam.getPathsSplit()) {
@ -679,12 +680,12 @@ public class FhirTerser {
}
}
}
return false;
}
private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath,
List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
if (theChildDefinition != null) {
theChildDefinitionPath.add(theChildDefinition);
}
@ -692,7 +693,7 @@ public class FhirTerser {
theElementDefinitionPath.add(theDefinition);
theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
Collections.unmodifiableList(theElementDefinitionPath));
Collections.unmodifiableList(theElementDefinitionPath));
/*
* Visit undeclared extensions
@ -710,85 +711,85 @@ public class FhirTerser {
* Now visit the children of the given element
*/
switch (theDefinition.getChildType()) {
case ID_DATATYPE:
case PRIMITIVE_XHTML_HL7ORG:
case PRIMITIVE_XHTML:
case PRIMITIVE_DATATYPE:
// These are primitive types, so we don't need to visit their children
break;
case RESOURCE:
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
if (values != null) {
for (IBase nextValue : values) {
if (nextValue == null) {
continue;
}
if (nextValue.isEmpty()) {
continue;
}
BaseRuntimeElementDefinition<?> childElementDef;
childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
if (childElementDef == null) {
StringBuilder b = new StringBuilder();
b.append("Found value of type[");
b.append(nextValue.getClass().getSimpleName());
b.append("] which is not valid for field[");
b.append(nextChild.getElementName());
b.append("] in ");
b.append(childDef.getName());
b.append(" - Valid types: ");
for (Iterator<String> iter = new TreeSet<String>(nextChild.getValidChildNames()).iterator(); iter.hasNext();) {
BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next());
b.append(childByName.getImplementingClass().getSimpleName());
if (iter.hasNext()) {
b.append(", ");
}
case ID_DATATYPE:
case PRIMITIVE_XHTML_HL7ORG:
case PRIMITIVE_XHTML:
case PRIMITIVE_DATATYPE:
// These are primitive types, so we don't need to visit their children
break;
case RESOURCE:
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
if (values != null) {
for (IBase nextValue : values) {
if (nextValue == null) {
continue;
}
throw new DataFormatException(b.toString());
}
if (nextValue.isEmpty()) {
continue;
}
BaseRuntimeElementDefinition<?> childElementDef;
childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
if (nextChild instanceof RuntimeChildDirectResource) {
// Don't descend into embedded resources
theContainingElementPath.add(nextValue);
theChildDefinitionPath.add(nextChild);
theElementDefinitionPath.add(myContext.getElementDefinition(nextValue.getClass()));
theCallback.acceptElement(nextValue, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
if (childElementDef == null) {
StringBuilder b = new StringBuilder();
b.append("Found value of type[");
b.append(nextValue.getClass().getSimpleName());
b.append("] which is not valid for field[");
b.append(nextChild.getElementName());
b.append("] in ");
b.append(childDef.getName());
b.append(" - Valid types: ");
for (Iterator<String> iter = new TreeSet<String>(nextChild.getValidChildNames()).iterator(); iter.hasNext(); ) {
BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next());
b.append(childByName.getImplementingClass().getSimpleName());
if (iter.hasNext()) {
b.append(", ");
}
}
throw new DataFormatException(b.toString());
}
if (nextChild instanceof RuntimeChildDirectResource) {
// Don't descend into embedded resources
theContainingElementPath.add(nextValue);
theChildDefinitionPath.add(nextChild);
theElementDefinitionPath.add(myContext.getElementDefinition(nextValue.getClass()));
theCallback.acceptElement(nextValue, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
Collections.unmodifiableList(theElementDefinitionPath));
theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
theContainingElementPath.remove(theContainingElementPath.size() - 1);
theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
} else {
visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
theContainingElementPath.remove(theContainingElementPath.size() - 1);
theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
} else {
visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
}
}
}
}
break;
}
break;
}
case CONTAINED_RESOURCES: {
BaseContainedDt value = (BaseContainedDt) theElement;
for (IResource next : value.getContainedResources()) {
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
case CONTAINED_RESOURCES: {
BaseContainedDt value = (BaseContainedDt) theElement;
for (IResource next : value.getContainedResources()) {
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
}
break;
}
break;
}
case EXTENSION_DECLARED:
case UNDECL_EXT: {
throw new IllegalStateException("state should not happen: " + theDefinition.getChildType());
}
case CONTAINED_RESOURCE_LIST: {
if (theElement != null) {
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
case EXTENSION_DECLARED:
case UNDECL_EXT: {
throw new IllegalStateException("state should not happen: " + theDefinition.getChildType());
}
case CONTAINED_RESOURCE_LIST: {
if (theElement != null) {
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
}
break;
}
break;
}
}
if (theChildDefinition != null) {
@ -800,16 +801,14 @@ public class FhirTerser {
/**
* Visit all elements in a given resource
*
*
* <p>
* Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
* {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
* </p>
*
* @param theResource
* The resource to visit
* @param theVisitor
* The visitor
*
* @param theResource The resource to visit
* @param theVisitor The visitor
*/
public void visit(IBaseResource theResource, IModelVisitor theVisitor) {
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
@ -818,18 +817,16 @@ public class FhirTerser {
/**
* Visit all elements in a given resource
*
* <p>
* THIS ALTERNATE METHOD IS STILL EXPERIMENTAL
*
*
* <p>
* Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
* {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
* </p>
*
* @param theResource
* The resource to visit
* @param theVisitor
* The visitor
*
* @param theResource The resource to visit
* @param theVisitor The visitor
*/
void visit(IBaseResource theResource, IModelVisitor2 theVisitor) {
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
@ -837,22 +834,22 @@ public class FhirTerser {
}
private void visit(IdentityHashMap<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
if (theStack.put(theElement, theElement) != null) {
return;
}
theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition);
BaseRuntimeElementDefinition<?> def = theDefinition;
if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
def = myContext.getElementDefinition(theElement.getClass());
}
if (theElement instanceof IBaseReference) {
IBaseResource target = ((IBaseReference)theElement).getResource();
IBaseResource target = ((IBaseReference) theElement).getResource();
if (target != null) {
if (target.getIdElement().hasIdPart() == false || target.getIdElement().isLocal()) {
RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target);
@ -860,71 +857,71 @@ public class FhirTerser {
}
}
}
switch (def.getChildType()) {
case ID_DATATYPE:
case PRIMITIVE_XHTML_HL7ORG:
case PRIMITIVE_XHTML:
case PRIMITIVE_DATATYPE:
// These are primitive types
break;
case RESOURCE:
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
List<?> values = nextChild.getAccessor().getValues(theElement);
if (values != null) {
for (Object nextValueObject : values) {
IBase nextValue;
try {
nextValue = (IBase) nextValueObject;
} catch (ClassCastException e) {
String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
throw new ClassCastException(s);
}
if (nextValue == null) {
continue;
}
if (nextValue.isEmpty()) {
continue;
}
BaseRuntimeElementDefinition<?> childElementDef;
childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
case ID_DATATYPE:
case PRIMITIVE_XHTML_HL7ORG:
case PRIMITIVE_XHTML:
case PRIMITIVE_DATATYPE:
// These are primitive types
break;
case RESOURCE:
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
if (childElementDef == null) {
childElementDef = myContext.getElementDefinition(nextValue.getClass());
}
List<?> values = nextChild.getAccessor().getValues(theElement);
if (values != null) {
for (Object nextValueObject : values) {
IBase nextValue;
try {
nextValue = (IBase) nextValueObject;
} catch (ClassCastException e) {
String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
throw new ClassCastException(s);
}
if (nextValue == null) {
continue;
}
if (nextValue.isEmpty()) {
continue;
}
BaseRuntimeElementDefinition<?> childElementDef;
childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
if (nextChild instanceof RuntimeChildDirectResource) {
// Don't descend into embedded resources
theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef);
} else {
visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback);
if (childElementDef == null) {
childElementDef = myContext.getElementDefinition(nextValue.getClass());
}
if (nextChild instanceof RuntimeChildDirectResource) {
// Don't descend into embedded resources
theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef);
} else {
visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback);
}
}
}
}
break;
}
break;
}
case CONTAINED_RESOURCES: {
BaseContainedDt value = (BaseContainedDt) theElement;
for (IResource next : value.getContainedResources()) {
def = myContext.getResourceDefinition(next);
visit(theStack, next, next, pathToElement, null, def, theCallback);
case CONTAINED_RESOURCES: {
BaseContainedDt value = (BaseContainedDt) theElement;
for (IResource next : value.getContainedResources()) {
def = myContext.getResourceDefinition(next);
visit(theStack, next, next, pathToElement, null, def, theCallback);
}
break;
}
case CONTAINED_RESOURCE_LIST:
case EXTENSION_DECLARED:
case UNDECL_EXT: {
throw new IllegalStateException("state should not happen: " + def.getChildType());
}
break;
}
case CONTAINED_RESOURCE_LIST:
case EXTENSION_DECLARED:
case UNDECL_EXT: {
throw new IllegalStateException("state should not happen: " + def.getChildType());
}
}
theStack.remove(theElement);
}
}

View File

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

View File

@ -61,7 +61,6 @@ ca.uhn.fhir.validation.ValidationResult.noIssuesDetected=No issues detected duri
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceVersionConstraintFailure=The operation has failed with a version constraint failure. This generally means that two clients/threads were trying to update the same resource at the same time, and this request was chosen as the failing request.
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceIndexedCompositeStringUniqueConstraintFailure=The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index.
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlInvalidResourceType=Invalid match URL "{0}" - Unknown resource type: "{1}"
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlNoMatches=Invalid match URL "{0}" - No resources match this search
@ -82,6 +81,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithInvalidId=Can not
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.incorrectResourceType=Incorrect resource type detected for endpoint, found {0} but expected {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithClientAssignedNumericId=Can not create resource with ID[{0}], no resource with this ID exists and clients may only assign IDs which contain at least one non-numeric character
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithClientAssignedId=Can not create resource with ID[{0}], ID must not be supplied on a create (POST) operation
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithClientAssignedIdNotAllowed=No resource exists on this server resource with ID[{0}], and client-assigned IDs are not enabled.
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidParameterChain=Invalid parameter chain: {0}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidVersion=Version "{0}" is not valid for resource {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.multipleParamsWithSameNameOneIsMissingTrue=This server does not know how to handle multiple "{0}" parameters where one has a value of :missing=true
@ -91,7 +91,8 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully update
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1}
ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}

View File

@ -0,0 +1,67 @@
package ca.uhn.fhir.rest.param;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
@RunWith(JUnit4.class)
public class DateRangeParamTest {
private FhirContext fhirContext;
@Before
public void initMockContext() {
fhirContext = Mockito.mock(FhirContext.class);
}
/** Can happen e.g. when the query parameter for {@code _lastUpdated} is left empty. */
@Test
public void testParamWithoutPrefixAndWithoutValue() {
QualifiedParamList qualifiedParamList = new QualifiedParamList(1);
qualifiedParamList.add("");
List<QualifiedParamList> params = new ArrayList<>(1);
params.add(qualifiedParamList);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setValuesAsQueryTokens(fhirContext, "_lastUpdated", params);
assertTrue(dateRangeParam.isEmpty());
}
/** Can happen e.g. when the query parameter for {@code _lastUpdated} is given as {@code lt} without any value. */
@Test
public void testUpperBoundWithPrefixWithoutValue() {
QualifiedParamList qualifiedParamList = new QualifiedParamList(1);
qualifiedParamList.add("lt");
List<QualifiedParamList> params = new ArrayList<>(1);
params.add(qualifiedParamList);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setValuesAsQueryTokens(fhirContext, "_lastUpdated", params);
assertTrue(dateRangeParam.isEmpty());
}
/** Can happen e.g. when the query parameter for {@code _lastUpdated} is given as {@code gt} without any value. */
@Test
public void testLowerBoundWithPrefixWithoutValue() {
QualifiedParamList qualifiedParamList = new QualifiedParamList(1);
qualifiedParamList.add("gt");
List<QualifiedParamList> params = new ArrayList<>(1);
params.add(qualifiedParamList);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setValuesAsQueryTokens(fhirContext, "_lastUpdated", params);
assertTrue(dateRangeParam.isEmpty());
}
}

View File

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

View File

@ -256,27 +256,30 @@ public abstract class BaseApp {
System.err.println("" + ansi().fg(Ansi.Color.WHITE).boldOff());
logCommandUsageNoHeader(command);
runCleanupHookAndUnregister();
System.exit(1);
exitDueToException(e);
} catch (CommandFailureException e) {
ourLog.error(e.getMessage());
runCleanupHookAndUnregister();
if ("true".equals(System.getProperty("test"))) {
throw e;
} else {
System.exit(1);
}
exitDueToException(e);
} catch (Throwable t) {
ourLog.error("Error during execution: ", t);
runCleanupHookAndUnregister();
if ("true".equals(System.getProperty("test"))) {
throw new CommandFailureException("Error: " + t.toString(), t);
} else {
System.exit(1);
}
exitDueToException(new CommandFailureException("Error: " + t.toString(), t));
}
}
private void exitDueToException(Throwable e) {
if ("true".equals(System.getProperty("test"))) {
if (e instanceof CommandFailureException) {
throw (CommandFailureException)e;
}
throw new Error(e);
} else {
System.exit(1);
}
}
private void runCleanupHookAndUnregister() {
if (myShutdownHookHasNotRun) {
Runtime.getRuntime().removeShutdownHook(myShutdownHook);

View File

@ -25,15 +25,24 @@ import ca.uhn.fhir.jpa.migrate.Migrator;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
public abstract class BaseMigrateDatabaseCommand<T extends Enum> extends BaseCommand {
private static final String MIGRATE_DATABASE = "migrate-database";
private Set<String> myFlags;
protected Set<String> getFlags() {
return myFlags;
}
@Override
public String getCommandDescription() {
@ -68,6 +77,7 @@ public abstract class BaseMigrateDatabaseCommand<T extends Enum> extends BaseCom
addRequiredOption(retVal, "f", "from", "Version", "The database schema version to migrate FROM");
addRequiredOption(retVal, "t", "to", "Version", "The database schema version to migrate TO");
addRequiredOption(retVal, "d", "driver", "Driver", "The database driver to use (Options are " + driverOptions() + ")");
addOptionalOption(retVal, "x", "flags", "Flags", "A comma-separated list of any specific migration flags (these flags are version specific, see migrator documentation for details)");
return retVal;
}
@ -97,6 +107,12 @@ public abstract class BaseMigrateDatabaseCommand<T extends Enum> extends BaseCom
boolean dryRun = theCommandLine.hasOption("r");
String flags = theCommandLine.getOptionValue("x");
myFlags = Arrays.stream(defaultString(flags).split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
Migrator migrator = new Migrator();
migrator.setConnectionUrl(url);
migrator.setDriverType(driverType);

View File

@ -42,7 +42,7 @@ public class HapiMigrateDatabaseCommand extends BaseMigrateDatabaseCommand<Versi
@Override
protected void addTasks(Migrator theMigrator, VersionEnum theFrom, VersionEnum theTo) {
List<BaseTask<?>> tasks = new HapiFhirJpaMigrationTasks().getTasks(theFrom, theTo);
List<BaseTask<?>> tasks = new HapiFhirJpaMigrationTasks(getFlags()).getTasks(theFrom, theTo);
tasks.forEach(theMigrator::addTask);
}
}

View File

@ -7,14 +7,26 @@ import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedStatementCallback;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobCreator;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class HapiMigrateDatabaseCommandTest {
@ -25,39 +37,64 @@ public class HapiMigrateDatabaseCommandTest {
}
@Test
public void testMigrate() throws IOException {
public void testMigrate_340_370() throws IOException {
File directory = new File("target/migrator_derby_test_340_360");
if (directory.exists()) {
FileUtils.deleteDirectory(directory);
}
String url = "jdbc:derby:directory:" + directory.getAbsolutePath() + ";create=true";
DriverTypeEnum.ConnectionProperties connectionProperties = DriverTypeEnum.DERBY_EMBEDDED.newConnectionProperties(url, "", "");
String initSql = "/persistence_create_derby107_340.sql";
executeSqlStatements(connectionProperties, initSql);
seedDatabase340(connectionProperties);
ourLog.info("**********************************************");
ourLog.info("Done Setup, Starting Migration...");
ourLog.info("**********************************************");
String[] args = new String[]{
"migrate-database",
"-d", "DERBY_EMBEDDED",
"-u", url,
"-n", "",
"-p", "",
"-f", "V3_4_0",
"-t", "V3_7_0"
};
App.main(args);
connectionProperties.getTxTemplate().execute(t -> {
JdbcTemplate jdbcTemplate = connectionProperties.newJdbcTemplate();
List<Map<String, Object>> values = jdbcTemplate.queryForList("SELECT * FROM hfj_spidx_token");
assertEquals(1, values.size());
assertEquals("identifier", values.get(0).get("SP_NAME"));
assertEquals("12345678", values.get(0).get("SP_VALUE"));
assertTrue(values.get(0).keySet().contains("HASH_IDENTITY"));
assertEquals(7001889285610424179L, values.get(0).get("HASH_IDENTITY"));
return null;
});
}
@Test
public void testMigrate_340_350() throws IOException {
File directory = new File("target/migrator_derby_test_340_350");
if (directory.exists()) {
FileUtils.deleteDirectory(directory);
}
String url = "jdbc:derby:directory:target/migrator_derby_test_340_350;create=true";
String url = "jdbc:derby:directory:" + directory.getAbsolutePath() + ";create=true";
DriverTypeEnum.ConnectionProperties connectionProperties = DriverTypeEnum.DERBY_EMBEDDED.newConnectionProperties(url, "", "");
String script = IOUtils.toString(HapiMigrateDatabaseCommandTest.class.getResourceAsStream("/persistence_create_derby107_340.sql"), Charsets.UTF_8);
List<String> scriptStatements = new ArrayList<>(Arrays.asList(script.split("\n")));
for (int i = 0; i < scriptStatements.size(); i++) {
String nextStatement = scriptStatements.get(i);
if (isBlank(nextStatement)) {
scriptStatements.remove(i);
i--;
continue;
}
String initSql = "/persistence_create_derby107_340.sql";
executeSqlStatements(connectionProperties, initSql);
nextStatement = nextStatement.trim();
while (nextStatement.endsWith(";")) {
nextStatement = nextStatement.substring(0, nextStatement.length() - 1);
}
scriptStatements.set(i, nextStatement);
}
connectionProperties.getTxTemplate().execute(t -> {
for (String next : scriptStatements) {
connectionProperties.newJdbcTemplate().execute(next);
}
return null;
});
seedDatabase340(connectionProperties);
ourLog.info("**********************************************");
ourLog.info("Done Setup, Starting Dry Run...");
@ -75,6 +112,13 @@ public class HapiMigrateDatabaseCommandTest {
};
App.main(args);
connectionProperties.getTxTemplate().execute(t -> {
JdbcTemplate jdbcTemplate = connectionProperties.newJdbcTemplate();
List<Map<String, Object>> values = jdbcTemplate.queryForList("SELECT * FROM hfj_spidx_token");
assertFalse(values.get(0).keySet().contains("HASH_IDENTITY"));
return null;
});
ourLog.info("**********************************************");
ourLog.info("Done Setup, Starting Migration...");
ourLog.info("**********************************************");
@ -89,5 +133,203 @@ public class HapiMigrateDatabaseCommandTest {
"-t", "V3_5_0"
};
App.main(args);
connectionProperties.getTxTemplate().execute(t -> {
JdbcTemplate jdbcTemplate = connectionProperties.newJdbcTemplate();
List<Map<String, Object>> values = jdbcTemplate.queryForList("SELECT * FROM hfj_spidx_token");
assertEquals(1, values.size());
assertEquals("identifier", values.get(0).get("SP_NAME"));
assertEquals("12345678", values.get(0).get("SP_VALUE"));
assertTrue(values.get(0).keySet().contains("HASH_IDENTITY"));
assertEquals(7001889285610424179L, values.get(0).get("HASH_IDENTITY"));
return null;
});
}
private void seedDatabase340(DriverTypeEnum.ConnectionProperties theConnectionProperties) {
theConnectionProperties.getTxTemplate().execute(t -> {
JdbcTemplate jdbcTemplate = theConnectionProperties.newJdbcTemplate();
jdbcTemplate.execute(
"insert into HFJ_RESOURCE (RES_DELETED_AT, RES_VERSION, FORCED_ID_PID, HAS_TAGS, RES_PUBLISHED, RES_UPDATED, SP_HAS_LINKS, HASH_SHA256, SP_INDEX_STATUS, RES_LANGUAGE, SP_CMPSTR_UNIQ_PRESENT, SP_COORDS_PRESENT, SP_DATE_PRESENT, SP_NUMBER_PRESENT, SP_QUANTITY_PRESENT, SP_STRING_PRESENT, SP_TOKEN_PRESENT, SP_URI_PRESENT, RES_PROFILE, RES_TYPE, RES_VER, RES_ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(new DefaultLobHandler()) {
@Override
protected void setValues(PreparedStatement thePs, LobCreator theLobCreator) throws SQLException {
thePs.setNull(1, Types.TIMESTAMP);
thePs.setString(2, "R4");
thePs.setNull(3, Types.BIGINT);
thePs.setBoolean(4, false);
thePs.setTimestamp(5, new Timestamp(System.currentTimeMillis()));
thePs.setTimestamp(6, new Timestamp(System.currentTimeMillis()));
thePs.setBoolean(7, false);
thePs.setNull(8, Types.VARCHAR);
thePs.setLong(9, 1L);
thePs.setNull(10, Types.VARCHAR);
thePs.setBoolean(11, false);
thePs.setBoolean(12, false);
thePs.setBoolean(13, false);
thePs.setBoolean(14, false);
thePs.setBoolean(15, false);
thePs.setBoolean(16, false);
thePs.setBoolean(17, false);
thePs.setBoolean(18, false);
thePs.setNull(19, Types.VARCHAR);
thePs.setString(20, "Patient");
thePs.setLong(21, 1L);
thePs.setLong(22, 1L);
}
}
);
jdbcTemplate.execute(
"insert into HFJ_RES_VER (RES_DELETED_AT, RES_VERSION, FORCED_ID_PID, HAS_TAGS, RES_PUBLISHED, RES_UPDATED, RES_ENCODING, RES_TEXT, RES_ID, RES_TYPE, RES_VER, PID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(new DefaultLobHandler()) {
@Override
protected void setValues(PreparedStatement thePs, LobCreator theLobCreator) throws SQLException {
thePs.setNull(1, Types.TIMESTAMP);
thePs.setString(2, "R4");
thePs.setNull(3, Types.BIGINT);
thePs.setBoolean(4, false);
thePs.setTimestamp(5, new Timestamp(System.currentTimeMillis()));
thePs.setTimestamp(6, new Timestamp(System.currentTimeMillis()));
thePs.setString(7, "JSON");
theLobCreator.setBlobAsBytes(thePs, 8, "{\"resourceType\":\"Patient\"}".getBytes(Charsets.US_ASCII));
thePs.setLong(9, 1L);
thePs.setString(10, "Patient");
thePs.setLong(11, 1L);
thePs.setLong(12, 1L);
}
}
);
jdbcTemplate.execute(
"insert into HFJ_SPIDX_STRING (SP_MISSING, SP_NAME, RES_ID, RES_TYPE, SP_UPDATED, SP_VALUE_EXACT, SP_VALUE_NORMALIZED, SP_ID) values (?, ?, ?, ?, ?, ?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(new DefaultLobHandler()) {
@Override
protected void setValues(PreparedStatement thePs, LobCreator theLobCreator) throws SQLException {
thePs.setBoolean(1, false);
thePs.setString(2, "given");
thePs.setLong(3, 1L); // res-id
thePs.setString(4, "Patient");
thePs.setTimestamp(5, new Timestamp(System.currentTimeMillis()));
thePs.setString(6, "ROBERT");
thePs.setString(7, "Robert");
thePs.setLong(8, 1L);
}
}
);
jdbcTemplate.execute(
"insert into HFJ_SPIDX_TOKEN (SP_MISSING, SP_NAME, RES_ID, RES_TYPE, SP_UPDATED, SP_SYSTEM, SP_VALUE, SP_ID) values (?, ?, ?, ?, ?, ?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(new DefaultLobHandler()) {
@Override
protected void setValues(PreparedStatement thePs, LobCreator theLobCreator) throws SQLException {
thePs.setBoolean(1, false);
thePs.setString(2, "identifier");
thePs.setLong(3, 1L); // res-id
thePs.setString(4, "Patient");
thePs.setTimestamp(5, new Timestamp(System.currentTimeMillis()));
thePs.setString(6, "http://foo");
thePs.setString(7, "12345678");
thePs.setLong(8, 1L);
}
}
);
jdbcTemplate.execute(
"insert into HFJ_SPIDX_DATE (SP_MISSING, SP_NAME, RES_ID, RES_TYPE, SP_UPDATED, SP_VALUE_HIGH, SP_VALUE_LOW, SP_ID) values (?, ?, ?, ?, ?, ?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(new DefaultLobHandler()) {
@Override
protected void setValues(PreparedStatement thePs, LobCreator theLobCreator) throws SQLException {
thePs.setBoolean(1, false);
thePs.setString(2, "birthdate");
thePs.setLong(3, 1L); // res-id
thePs.setString(4, "Patient");
thePs.setTimestamp(5, new Timestamp(System.currentTimeMillis()));
thePs.setTimestamp(6, new Timestamp(1000000000L)); // value high
thePs.setTimestamp(7, new Timestamp(1000000000L)); // value low
thePs.setLong(8, 1L);
}
}
);
return null;
});
}
@Test
public void testMigrate_340_350_NoMigrateHashes() throws IOException {
File directory = new File("target/migrator_derby_test_340_350_nmh");
if (directory.exists()) {
FileUtils.deleteDirectory(directory);
}
String url = "jdbc:derby:directory:" + directory.getAbsolutePath() + ";create=true";
DriverTypeEnum.ConnectionProperties connectionProperties = DriverTypeEnum.DERBY_EMBEDDED.newConnectionProperties(url, "", "");
String initSql = "/persistence_create_derby107_340.sql";
executeSqlStatements(connectionProperties, initSql);
seedDatabase340(connectionProperties);
ourLog.info("**********************************************");
ourLog.info("Done Setup, Starting Migration...");
ourLog.info("**********************************************");
String[] args = new String[]{
"migrate-database",
"-d", "DERBY_EMBEDDED",
"-u", url,
"-n", "",
"-p", "",
"-f", "V3_4_0",
"-t", "V3_5_0",
"-x", "no-migrate-350-hashes"
};
App.main(args);
connectionProperties.getTxTemplate().execute(t -> {
JdbcTemplate jdbcTemplate = connectionProperties.newJdbcTemplate();
List<Map<String, Object>> values = jdbcTemplate.queryForList("SELECT * FROM hfj_spidx_token");
assertEquals(1, values.size());
assertEquals("identifier", values.get(0).get("SP_NAME"));
assertEquals("12345678", values.get(0).get("SP_VALUE"));
assertEquals(null, values.get(0).get("HASH_IDENTITY"));
return null;
});
}
private void executeSqlStatements(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theInitSql) throws
IOException {
String script = IOUtils.toString(HapiMigrateDatabaseCommandTest.class.getResourceAsStream(theInitSql), Charsets.UTF_8);
List<String> scriptStatements = new ArrayList<>(Arrays.asList(script.split("\n")));
for (int i = 0; i < scriptStatements.size(); i++) {
String nextStatement = scriptStatements.get(i);
if (isBlank(nextStatement)) {
scriptStatements.remove(i);
i--;
continue;
}
nextStatement = nextStatement.trim();
while (nextStatement.endsWith(";")) {
nextStatement = nextStatement.substring(0, nextStatement.length() - 1);
}
scriptStatements.set(i, nextStatement);
}
theConnectionProperties.getTxTemplate().execute(t -> {
for (String next : scriptStatements) {
theConnectionProperties.newJdbcTemplate().execute(next);
}
return null;
});
}
}

View File

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

View File

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

View File

@ -37,7 +37,7 @@ public class FhirDbConfig {
return retVal;
}
@Bean()
@Bean
public Properties jpaProperties() {
Properties extraProperties = new Properties();
extraProperties.put("hibernate.dialect", DerbyTenSevenHapiFhirDialect.class.getName());

View File

@ -36,7 +36,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
/**
* Configure FHIR properties around the the JPA server via this bean
*/
@Bean()
@Bean
public DaoConfig daoConfig() {
DaoConfig retVal = new DaoConfig();
retVal.setSubscriptionEnabled(true);
@ -47,13 +47,11 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
}
@Override
@Bean()
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
retVal.setDataSource(myDataSource);
retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity");
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
retVal.setJpaProperties(myJpaProperties);
return retVal;
}
@ -87,7 +85,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
return retVal;
}
@Bean()
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);

View File

@ -2,14 +2,18 @@ package ca.uhn.fhir.jpa.demo;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -42,7 +46,7 @@ public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
/**
* Configure FHIR properties around the the JPA server via this bean
*/
@Bean()
@Bean
public DaoConfig daoConfig() {
DaoConfig retVal = new DaoConfig();
retVal.setSubscriptionEnabled(true);
@ -52,8 +56,13 @@ public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
return retVal;
}
@Bean
public ModelConfig modelConfig() {
return daoConfig().getModelConfig();
}
@Override
@Bean()
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
@ -90,11 +99,10 @@ public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
return retVal;
}
@Bean()
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);
return retVal;
}
}

View File

@ -42,7 +42,7 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 {
/**
* Configure FHIR properties around the the JPA server via this bean
*/
@Bean()
@Bean
public DaoConfig daoConfig() {
DaoConfig retVal = new DaoConfig();
retVal.setSubscriptionEnabled(true);
@ -53,7 +53,7 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 {
}
@Override
@Bean()
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
@ -90,7 +90,7 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 {
return retVal;
}
@Bean()
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,49 +20,11 @@ package ca.uhn.fhir.rest.client.impl;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.util.XmlDetectionUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
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.RuntimeResourceDefinition;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.client.api.*;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
@ -71,8 +33,21 @@ import ca.uhn.fhir.rest.client.method.IClientResponseHandler;
import ca.uhn.fhir.rest.client.method.IClientResponseHandlerHandlesBinary;
import ca.uhn.fhir.rest.client.method.MethodUtil;
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.XmlUtil;
import ca.uhn.fhir.util.XmlDetectionUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import java.io.*;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseClient implements IRestfulClient {
@ -86,16 +61,17 @@ public abstract class BaseClient implements IRestfulClient {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class);
private final IHttpClient myClient;
private final RestfulClientFactory myFactory;
private final String myUrlBase;
private boolean myDontValidateConformance;
private EncodingEnum myEncoding = null; // default unspecified (will be XML)
private final RestfulClientFactory myFactory;
private List<IClientInterceptor> myInterceptors = new ArrayList<IClientInterceptor>();
private boolean myKeepResponses = false;
private IHttpResponse myLastResponse;
private String myLastResponseBody;
private Boolean myPrettyPrint = false;
private SummaryEnum mySummary;
private final String myUrlBase;
private RequestFormatParamStyleEnum myRequestFormatParamStyle = RequestFormatParamStyleEnum.SHORT;
BaseClient(IHttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) {
super();
@ -118,13 +94,17 @@ public abstract class BaseClient implements IRestfulClient {
}
protected Map<String, List<String>> createExtraParams() {
HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
protected Map<String, List<String>> createExtraParams(String theCustomAcceptHeader) {
HashMap<String, List<String>> retVal = new LinkedHashMap<>();
if (getEncoding() == EncodingEnum.XML) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
if (isBlank(theCustomAcceptHeader)) {
if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
if (getEncoding() == EncodingEnum.XML) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
}
}
if (isPrettyPrint()) {
@ -138,7 +118,7 @@ public abstract class BaseClient implements IRestfulClient {
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
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() {
@ -150,6 +130,17 @@ public abstract class BaseClient implements IRestfulClient {
return myEncoding;
}
/**
* Sets the encoding that will be used on requests. Default is <code>null</code>, which means the client will not
* explicitly request an encoding. (This is perfectly acceptable behaviour according to the FHIR specification. In
* this case, the server will choose which encoding to return, and the client can handle either XML or JSON)
*/
@Override
public void setEncoding(EncodingEnum theEncoding) {
myEncoding = theEncoding;
// return this;
}
/**
* {@inheritDoc}
*/
@ -192,20 +183,31 @@ public abstract class BaseClient implements IRestfulClient {
return mySummary;
}
@Override
public void setSummary(SummaryEnum theSummary) {
mySummary = theSummary;
}
public String getUrlBase() {
return myUrlBase;
}
@Override
public void setFormatParamStyle(RequestFormatParamStyleEnum theRequestFormatParamStyle) {
Validate.notNull(theRequestFormatParamStyle, "theRequestFormatParamStyle must not be null");
myRequestFormatParamStyle = theRequestFormatParamStyle;
}
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation) {
return invokeClient(theContext, binding, clientInvocation, false);
}
<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,
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective) {
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective, String theCustomAcceptHeader) {
if (!myDontValidateConformance) {
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
@ -216,13 +218,15 @@ public abstract class BaseClient implements IRestfulClient {
IHttpRequest httpRequest = null;
IHttpResponse response = null;
try {
Map<String, List<String>> params = createExtraParams();
Map<String, List<String>> params = createExtraParams(theCustomAcceptHeader);
if (clientInvocation instanceof HttpGetClientInvocation) {
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT && isBlank(theCustomAcceptHeader)) {
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
}
}
@ -247,12 +251,17 @@ public abstract class BaseClient implements IRestfulClient {
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
if (isNotBlank(theCustomAcceptHeader)) {
httpRequest.removeHeaders(Constants.HEADER_ACCEPT);
httpRequest.addHeader(Constants.HEADER_ACCEPT, theCustomAcceptHeader);
}
if (theCacheControlDirective != null) {
StringBuilder b = new StringBuilder();
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache());
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_STORE, theCacheControlDirective.isNoStore());
if (theCacheControlDirective.getMaxResults() != null) {
addToCacheControlHeader(b, Constants.CACHE_CONTROL_MAX_RESULTS+"="+ Integer.toString(theCacheControlDirective.getMaxResults().intValue()), true);
addToCacheControlHeader(b, Constants.CACHE_CONTROL_MAX_RESULTS + "=" + Integer.toString(theCacheControlDirective.getMaxResults().intValue()), true);
}
if (b.length() > 0) {
httpRequest.addHeader(Constants.HEADER_CACHE_CONTROL, b.toString());
@ -288,14 +297,10 @@ public abstract class BaseClient implements IRestfulClient {
if (response.getStatus() < 200 || response.getStatus() > 299) {
String body = null;
Reader reader = null;
try {
reader = response.createReader();
try (Reader reader = response.createReader()) {
body = IOUtils.toString(reader);
} catch (Exception e) {
ourLog.debug("Failed to read input stream", e);
} finally {
IOUtils.closeQuietly(reader);
}
String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo();
@ -333,27 +338,24 @@ public abstract class BaseClient implements IRestfulClient {
if (binding instanceof IClientResponseHandlerHandlesBinary) {
IClientResponseHandlerHandlesBinary<T> handlesBinary = (IClientResponseHandlerHandlesBinary<T>) binding;
if (handlesBinary.isBinary()) {
InputStream reader = response.readEntity();
try {
return handlesBinary.invokeClient(mimeType, reader, response.getStatus(), headers);
} finally {
IOUtils.closeQuietly(reader);
try (InputStream reader = response.readEntity()) {
return handlesBinary.invokeClientForBinary(mimeType, reader, response.getStatus(), headers);
}
}
}
Reader reader = response.createReader();
try (InputStream inputStream = response.readEntity()) {
InputStream inputStreamToReturn = inputStream;
if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
String responseString = IOUtils.toString(reader);
keepResponseAndLogIt(theLogRequestAndResponse, response, responseString);
reader = new StringReader(responseString);
}
if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
if (inputStream != null) {
String responseString = IOUtils.toString(inputStream, Charsets.UTF_8);
keepResponseAndLogIt(theLogRequestAndResponse, response, responseString);
inputStreamToReturn = new ByteArrayInputStream(responseString.getBytes(Charsets.UTF_8));
}
}
try {
return binding.invokeClient(mimeType, reader, response.getStatus(), headers);
} finally {
IOUtils.closeQuietly(reader);
return binding.invokeClient(mimeType, inputStreamToReturn, response.getStatus(), headers);
}
} catch (DataFormatException e) {
@ -397,6 +399,13 @@ public abstract class BaseClient implements IRestfulClient {
return myKeepResponses;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/
public void setKeepResponses(boolean theKeepResponses) {
myKeepResponses = theKeepResponses;
}
/**
* Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
@ -406,6 +415,17 @@ public abstract class BaseClient implements IRestfulClient {
return Boolean.TRUE.equals(myPrettyPrint);
}
/**
* Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
* servers which might implement it).
*/
@Override
public void setPrettyPrint(Boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
// return this;
}
private void keepResponseAndLogIt(boolean theLogRequestAndResponse, IHttpResponse response, String responseString) {
if (myKeepResponses) {
myLastResponse = response;
@ -438,56 +458,54 @@ public abstract class BaseClient implements IRestfulClient {
myDontValidateConformance = theDontValidateConformance;
}
/**
* Sets the encoding that will be used on requests. Default is <code>null</code>, which means the client will not
* explicitly request an encoding. (This is perfectly acceptable behaviour according to the FHIR specification. In
* this case, the server will choose which encoding to return, and the client can handle either XML or JSON)
*/
@Override
public void setEncoding(EncodingEnum theEncoding) {
myEncoding = theEncoding;
// return this;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/
public void setKeepResponses(boolean theKeepResponses) {
myKeepResponses = theKeepResponses;
}
/**
* Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
* servers which might implement it).
*/
@Override
public void setPrettyPrint(Boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
// return this;
}
@Override
public void setSummary(SummaryEnum theSummary) {
mySummary = theSummary;
}
@Override
public void unregisterInterceptor(IClientInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor);
}
static ArrayList<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> thePreferResponseType) {
ArrayList<Class<? extends IBaseResource>> preferResponseTypes = null;
if (thePreferResponseType != null) {
preferResponseTypes = new ArrayList<Class<? extends IBaseResource>>(1);
preferResponseTypes.add(thePreferResponseType);
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;
}
return preferResponseTypes;
}
protected final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
protected class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
private boolean myAllowHtmlResponse;
private IIdType myId;
@ -522,20 +540,20 @@ public abstract class BaseClient implements IRestfulClient {
}
@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);
if (respType == 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());
parser.setServerBaseUrl(getUrlBase());
if (myPreferResponseTypes != null) {
parser.setPreferTypes(myPreferResponseTypes);
}
T retVal = parser.parseResource(myReturnType, theResponseReader);
T retVal = parser.parseResource(myReturnType, theResponseInputStream);
MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
@ -543,7 +561,7 @@ public abstract class BaseClient implements IRestfulClient {
}
@SuppressWarnings("unchecked")
private T readHtmlResponse(Reader theResponseReader) {
private T readHtmlResponse(InputStream theResponseInputStream) {
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType);
IBaseResource instance = resDef.newInstance();
BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
@ -555,7 +573,7 @@ public abstract class BaseClient implements IRestfulClient {
BaseRuntimeElementDefinition<?> divElement = divChild.getChildByName("div");
IPrimitiveType<?> divInstance = (IPrimitiveType<?>) divElement.newInstance();
try {
divInstance.setValueAsString(IOUtils.toString(theResponseReader));
divInstance.setValueAsString(IOUtils.toString(theResponseInputStream, Charsets.UTF_8));
} catch (Exception e) {
throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e);
}
@ -563,9 +581,19 @@ public abstract class BaseClient implements IRestfulClient {
return (T) instance;
}
public void setPreferResponseTypes(List<Class<? extends IBaseResource>> thePreferResponseTypes) {
public ResourceResponseHandler<T> setPreferResponseTypes(List<Class<? extends IBaseResource>> thePreferResponseTypes) {
myPreferResponseTypes = thePreferResponseTypes;
return this;
}
}
static ArrayList<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> thePreferResponseType) {
ArrayList<Class<? extends IBaseResource>> preferResponseTypes = null;
if (thePreferResponseType != null) {
preferResponseTypes = new ArrayList<Class<? extends IBaseResource>>(1);
preferResponseTypes.add(thePreferResponseType);
}
return preferResponseTypes;
}
}

View File

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

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.method;
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
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);
if (encoding == null) {
NonFhirResponseException ex = NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
populateException(ex, theResponseReader);
NonFhirResponseException ex = NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
populateException(ex, theResponseInputStream);
throw ex;
}
@ -139,7 +140,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
return mySupportsConditionalMultiple;
}
protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, Reader theResponseReader) {
protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, InputStream theResponseInputStream) {
BaseServerResponseException ex;
switch (theStatusCode) {
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");
break;
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
BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseReader);
BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseInputStream);
ex = new UnprocessableEntityException(myContext, operationOutcome);
break;
default:
@ -168,7 +169,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
break;
}
populateException(ex, theResponseReader);
populateException(ex, theResponseInputStream);
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);
}
private static void populateException(BaseServerResponseException theEx, Reader theResponseReader) {
private static void populateException(BaseServerResponseException theEx, InputStream theResponseInputStream) {
try {
String responseText = IOUtils.toString(theResponseReader);
String responseText = IOUtils.toString(theResponseInputStream);
theEx.setResponseBody(responseText);
} catch (IOException e) {
ourLog.debug("Failed to read response", e);

View File

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

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.client.method;
* #L%
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@ -123,21 +125,21 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
public abstract ReturnTypeEnum getReturnType();
@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) {
return toReturnType(null);
}
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theResponseStatusCode, myPreferTypesList);
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseInputStream, theResponseStatusCode, myPreferTypesList);
switch (getReturnType()) {
case BUNDLE: {
IBaseBundle bundle = null;
List<? extends IBaseResource> listOfResources = null;
IBaseBundle bundle;
List<? extends IBaseResource> listOfResources;
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);
switch (getMethodReturnType()) {
@ -171,9 +173,9 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
case RESOURCE: {
IBaseResource resource;
if (myResourceType != null) {
resource = parser.parseResource(myResourceType, theResponseReader);
resource = parser.parseResource(myResourceType, theResponseInputStream);
} else {
resource = parser.parseResource(theResponseReader);
resource = parser.parseResource(theResponseInputStream);
}
MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, resource);

View File

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

View File

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

View File

@ -49,15 +49,6 @@ import ca.uhn.fhir.util.*;
public class MethodUtil {
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 */
private MethodUtil() {
@ -497,8 +488,8 @@ public class MethodUtil {
}
public static MethodOutcome process2xxResponse(FhirContext theContext, int theResponseStatusCode,
String theResponseMimeType, Reader theResponseReader, Map<String, List<String>> theHeaders) {
List<String> locationHeaders = new ArrayList<String>();
String theResponseMimeType, InputStream theResponseReader, Map<String, List<String>> theHeaders) {
List<String> locationHeaders = new ArrayList<>();
List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC);
if (lh != null) {
locationHeaders.addAll(lh);
@ -509,14 +500,14 @@ public class MethodUtil {
}
MethodOutcome retVal = new MethodOutcome();
if (locationHeaders != null && locationHeaders.size() > 0) {
if (locationHeaders.size() > 0) {
String locationHeader = locationHeaders.get(0);
BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, locationHeader);
}
if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType);
if (ct != null) {
PushbackReader reader = new PushbackReader(theResponseReader);
PushbackInputStream reader = new PushbackInputStream(theResponseReader);
try {
int firstByte = reader.read();

View File

@ -106,7 +106,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
}
@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 {
byte[] contents = IOUtils.toByteArray(theResponseReader);

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0-SNAPSHOT</version>
<version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -76,8 +76,23 @@
<artifactId>hapi-fhir-client-okhttp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-subscription</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-searchparam</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
</dependency>
@ -303,6 +318,9 @@
<include>hapi-fhir-structures-hl7org-dstu2/target/jacoco.exec</include>
<include>hapi-fhir-structures-dstu3/target/jacoco.exec</include>
<include>hapi-fhir-structures-r4/target/jacoco.exec</include>
<include>hapi-fhir-jpaserver-model/target/jacoco.exec</include>
<include>hapi-fhir-jpaserver-searchparam/target/jacoco.exec</include>
<include>hapi-fhir-jpaserver-subscription/target/jacoco.exec</include>
<include>hapi-fhir-jpaserver-base/target/jacoco.exec</include>
<include>hapi-fhir-client-okhttp/target/jacoco.exec</include>
<include>hapi-fhir-android/target/jacoco.exec</include>
@ -341,6 +359,9 @@
<sourceDirectory>../hapi-fhir-structures-hl7org-dstu2/src/test/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-structures-dstu3/src/test/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-structures-r4/src/test/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-jpaserver-model/src/main/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-jpaserver-searchparam/src/main/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-jpaserver-subscription/src/main/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-jpaserver-base/src/main/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-client-okhttp/src/main/java</sourceDirectory>
</sourceDirectories>
@ -368,6 +389,9 @@
<source>../hapi-fhir-base/src/main/java</source>
<source>../hapi-fhir-client/src/main/java</source>
<source>../hapi-fhir-server/src/main/java</source>
<source>../hapi-fhir-jpaserver-model/src/main/java</source>
<source>../hapi-fhir-jpaserver-searchparam/src/main/java</source>
<source>../hapi-fhir-jpaserver-subscription/src/main/java</source>
<source>../hapi-fhir-jpaserver-base/src/main/java</source>
</sources>
</configuration>
@ -395,6 +419,15 @@
<testResource>
<directory>../hapi-fhir-base/src/test/resources</directory>
</testResource>
<testResource>
<directory>../hapi-fhir-jpaserver-model/src/test/resources</directory>
</testResource>
<testResource>
<directory>../hapi-fhir-jpaserver-searchparam/src/test/resources</directory>
</testResource>
<testResource>
<directory>../hapi-fhir-jpaserver-subscription/src/test/resources</directory>
</testResource>
<testResource>
<directory>../hapi-fhir-jpaserver-base/src/test/resources</directory>
</testResource>

View File

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

View File

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

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jaxrs.client;
import java.io.IOException;
import java.io.*;
/*
* #%L
@ -22,9 +22,6 @@ import java.io.IOException;
* #L%
*/
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -34,9 +31,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
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.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}
@ -118,7 +117,11 @@ public class JaxRsHttpResponse extends BaseHttpResponse implements IHttpResponse
@Override
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

View File

@ -22,6 +22,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L%
*/
import java.io.*;
import java.util.List;
import java.util.Map.Entry;
import javax.ws.rs.core.MediaType;
@ -104,8 +105,11 @@ public class JaxRsResponse extends RestfulResponse<JaxRsRequest> {
private ResponseBuilder buildResponse(int statusCode) {
ResponseBuilder response = Response.status(statusCode);
for (Entry<String, String> header : getHeaders().entrySet()) {
response.header(header.getKey(), header.getValue());
for (Entry<String, List<String>> header : getHeaders().entrySet()) {
final String key = header.getKey();
for (String value : header.getValue()) {
response.header(key, value);
}
}
return response;
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jaxrs.server.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
@ -10,6 +11,7 @@ import java.util.Set;
import javax.ws.rs.core.Response;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.junit.Before;
import org.junit.Test;
@ -108,10 +110,24 @@ public class JaxRsResponseTest {
assertEquals("application/xml+fhir; charset=UTF-8", result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
}
@Test
public void addMultipleHeaderValues() throws IOException {
response.addHeader("Authorization", "Basic");
response.addHeader("Authorization", "Bearer");
response.addHeader("Cache-Control", "no-cache, no-store");
final IBaseBinary binary = new Binary();
binary.setContentType("abc");
binary.setContent(new byte[] { 1 });
final Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theSummaryMode, 200, false, false, this.request);
assertThat(result.getHeaders().get("Authorization"), Matchers.contains("Basic", "Bearer"));
assertThat(result.getHeaders().get("Cache-Control"), Matchers.contains("no-cache, no-store"));
}
private Patient createPatient() {
Patient theResource = new Patient();
theResource.setId(new IdDt(15L));
return theResource;
}
}

View File

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

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>3.6.0-SNAPSHOT</version>
<version>3.7.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -70,6 +70,21 @@
<artifactId>hapi-fhir-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-subscription</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-searchparam</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation</artifactId>
@ -163,7 +178,7 @@
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<!-- For UCUM: TODO we should replace this with org.fhir UCUM -->
@ -550,74 +565,29 @@
</pluginManagement>
<plugins>
<plugin>
<groupId>de.juplo</groupId>
<artifactId>hibernate-maven-plugin</artifactId>
<groupId>de.jpdigital</groupId>
<artifactId>hibernate52-ddl-maven-plugin</artifactId>
<configuration>
<dialects>
<param>derby_10_7</param>
<param>postgresql92</param>
<param>mysql57</param>
<param>mariadb</param>
<param>oracle12c</param>
<param>sqlserver2012</param>
</dialects>
<outputDirectory>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database</outputDirectory>
<packages>
<param>ca.uhn.fhir.jpa.entity</param>
<param>ca.uhn.fhir.jpa.model.entity</param>
</packages>
</configuration>
<executions>
<execution>
<id>derby107</id>
<phase>process-classes</phase>
<goals>
<goal>create</goal>
<goal>gen-ddl</goal>
</goals>
<configuration>
<dialect>org.hibernate.dialect.DerbyTenSevenDialect</dialect>
<outputFile>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/persistence_create_derby107.sql</outputFile>
</configuration>
</execution>
<execution>
<id>postgres94</id>
<phase>process-classes</phase>
<goals>
<goal>create</goal>
</goals>
<configuration>
<dialect>org.hibernate.dialect.PostgreSQL94Dialect</dialect>
<outputFile>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/persistence_create_postgres94.sql</outputFile>
</configuration>
</execution>
<execution>
<id>mysql57</id>
<phase>process-classes</phase>
<goals>
<goal>create</goal>
</goals>
<configuration>
<dialect>org.hibernate.dialect.MySQL57Dialect</dialect>
<outputFile>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/persistence_create_mysql57.sql</outputFile>
</configuration>
</execution>
<execution>
<id>mariadb103</id>
<phase>process-classes</phase>
<goals>
<goal>create</goal>
</goals>
<configuration>
<dialect>org.hibernate.dialect.MariaDB103Dialect</dialect>
<outputFile>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/persistence_create_mariadb103.sql</outputFile>
</configuration>
</execution>
<execution>
<id>oracle12c</id>
<phase>process-classes</phase>
<goals>
<goal>create</goal>
</goals>
<configuration>
<dialect>org.hibernate.dialect.Oracle12cDialect</dialect>
<outputFile>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/persistence_create_oracle12c.sql</outputFile>
</configuration>
</execution>
<execution>
<id>sqlserver2012</id>
<phase>process-classes</phase>
<goals>
<goal>create</goal>
</goals>
<configuration>
<dialect>org.hibernate.dialect.SQLServer2012Dialect</dialect>
<outputFile>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/persistence_create_sqlserver2012.sql</outputFile>
</configuration>
</execution>
</executions>
<dependencies>
@ -670,7 +640,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<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>
</configuration>
</plugin>

View File

@ -1,5 +1,38 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.jpa.dao.DatabaseSearchParamProvider;
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.searchparam.registry.ISearchParamProvider;
import ca.uhn.fhir.jpa.subscription.config.BaseSubscriptionConfig;
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
* HAPI FHIR JPA Server
@ -20,49 +53,14 @@ package ca.uhn.fhir.jpa.config;
* #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.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 ca.uhn.fhir.jpa.util.IReindexController;
import ca.uhn.fhir.jpa.util.ReindexController;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
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.Map;
import java.util.concurrent.ScheduledExecutorService;
@Configuration
@EnableScheduling
@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 static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor";
@ -70,11 +68,6 @@ public abstract class BaseConfig implements SchedulingConfigurer {
@Autowired
protected Environment myEnv;
@Bean(name = "myDaoRegistry")
public DaoRegistry daoRegistry() {
return new DaoRegistry();
}
@Override
public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) {
theTaskRegistrar.setTaskScheduler(taskScheduler());
@ -91,44 +84,7 @@ public abstract class BaseConfig implements SchedulingConfigurer {
* factory with HAPI FHIR customizations
*/
protected LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean() {
@Override
public Map<String, Object> getJpaPropertyMap() {
Map<String, Object> retVal = super.getJpaPropertyMap();
if (!retVal.containsKey(AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE)) {
retVal.put(AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, LiteralHandlingMode.BIND);
}
if (!retVal.containsKey(AvailableSettings.CONNECTION_HANDLING)) {
retVal.put(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD);
}
/*
* Set some performance options
*/
if (!retVal.containsKey(AvailableSettings.STATEMENT_BATCH_SIZE)) {
retVal.put(AvailableSettings.STATEMENT_BATCH_SIZE, "30");
}
if (!retVal.containsKey(AvailableSettings.ORDER_INSERTS)) {
retVal.put(AvailableSettings.ORDER_INSERTS, "true");
}
if (!retVal.containsKey(AvailableSettings.ORDER_UPDATES)) {
retVal.put(AvailableSettings.ORDER_UPDATES, "true");
}
if (!retVal.containsKey(AvailableSettings.BATCH_VERSIONED_DATA)) {
retVal.put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
}
return retVal;
}
};
LocalContainerEntityManagerFactoryBean retVal = new HapiFhirLocalContainerEntityManagerFactoryBean();
configureEntityManagerFactory(retVal, fhirContext());
return retVal;
}
@ -136,54 +92,50 @@ public abstract class BaseConfig implements SchedulingConfigurer {
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
public IReindexController reindexController() {
return new ReindexController();
}
@Bean()
public ScheduledExecutorService scheduledExecutorService() {
public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
b.setPoolSize(5);
b.afterPropertiesSet();
return b.getObject();
return b;
}
@Bean(name="mySubscriptionTriggeringProvider")
@Bean(name = "mySubscriptionTriggeringProvider")
@Lazy
public SubscriptionTriggeringProvider subscriptionTriggeringProvider() {
return new SubscriptionTriggeringProvider();
}
@Bean(autowire = Autowire.BY_TYPE, name = "mySearchCoordinatorSvc")
public ISearchCoordinatorSvc searchCoordinatorSvc() {
return new SearchCoordinatorSvcImpl();
@Bean
public TaskScheduler taskScheduler() {
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
public ISearchParamPresenceSvc searchParamPresenceSvc() {
return new SearchParamPresenceSvcImpl();
public IResourceReindexingSvc resourceReindexingSvc() {
return new ResourceReindexingSvcImpl();
}
@Bean(autowire = Autowire.BY_TYPE)
@Bean
public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
return new StaleSearchDeletingSvcImpl();
}
@Bean
protected ISearchParamProvider searchParamProvider() {
return new DatabaseSearchParamProvider();
}
/**
* Note: If you're going to use this, you need to provide a bean
* of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
@ -207,30 +159,14 @@ public abstract class BaseConfig implements SchedulingConfigurer {
return new SubscriptionWebsocketInterceptor();
}
@Bean(name = TASK_EXECUTOR_NAME)
public TaskScheduler taskScheduler() {
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
retVal.setConcurrentExecutor(scheduledExecutorService());
retVal.setScheduledExecutor(scheduledExecutorService());
return retVal;
}
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.entity");
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
theFactory.setPersistenceProvider(new HibernatePersistenceProvider());
}
private static HibernateJpaDialect hibernateJpaDialect(HapiLocalizer theLocalizer) {
return new HapiFhirHibernateJpaDialect(theLocalizer);
}
/**
* This lets the "@Value" fields reference properties from the properties file
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}

View File

@ -1,7 +1,12 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu2;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
@ -111,7 +116,7 @@ public class BaseDstu2Config extends BaseConfig {
@Bean
public ISearchParamRegistry searchParamRegistry() {
return new SearchParamRegistryDstu2();
return new SearchParamRegistryDstu2(searchParamProvider());
}
@Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME)

View File

@ -21,8 +21,8 @@ package ca.uhn.fhir.jpa.config;
*/
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import org.hibernate.HibernateException;

View File

@ -0,0 +1,71 @@
package ca.uhn.fhir.jpa.config;
/*-
* #%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.hibernate.cfg.AvailableSettings;
import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import java.util.Map;
/**
* This class is an extension of the Spring/Hibernate LocalContainerEntityManagerFactoryBean
* that sets some sensible default property values
*/
public class HapiFhirLocalContainerEntityManagerFactoryBean extends LocalContainerEntityManagerFactoryBean {
@Override
public Map<String, Object> getJpaPropertyMap() {
Map<String, Object> retVal = super.getJpaPropertyMap();
if (!retVal.containsKey(AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE)) {
retVal.put(AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, LiteralHandlingMode.BIND);
}
if (!retVal.containsKey(AvailableSettings.CONNECTION_HANDLING)) {
retVal.put(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD);
}
/*
* Set some performance options
*/
if (!retVal.containsKey(AvailableSettings.STATEMENT_BATCH_SIZE)) {
retVal.put(AvailableSettings.STATEMENT_BATCH_SIZE, "30");
}
if (!retVal.containsKey(AvailableSettings.ORDER_INSERTS)) {
retVal.put(AvailableSettings.ORDER_INSERTS, "true");
}
if (!retVal.containsKey(AvailableSettings.ORDER_UPDATES)) {
retVal.put(AvailableSettings.ORDER_UPDATES, "true");
}
if (!retVal.containsKey(AvailableSettings.BATCH_VERSIONED_DATA)) {
retVal.put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
}
return retVal;
}
}

View File

@ -3,11 +3,15 @@ package ca.uhn.fhir.jpa.config.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
@ -119,7 +123,7 @@ public class BaseDstu3Config extends BaseConfig {
@Bean
public ISearchParamRegistry searchParamRegistry() {
return new SearchParamRegistryDstu3();
return new SearchParamRegistryDstu3(searchParamProvider());
}
@Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME)

View File

@ -3,12 +3,16 @@ package ca.uhn.fhir.jpa.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
@ -134,7 +138,7 @@ public class BaseR4Config extends BaseConfig {
@Bean
public ISearchParamRegistry searchParamRegistry() {
return new SearchParamRegistryR4();
return new SearchParamRegistryR4(searchParamProvider());
}
@Bean(name = "mySystemDaoR4", autowire = Autowire.BY_NAME)

View File

@ -25,14 +25,16 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
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.dao.r4.MatchResourceUrlService;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.jpa.util.IReindexController;
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
import ca.uhn.fhir.model.api.*;
@ -42,7 +44,6 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.param.QualifierDetails;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
@ -54,16 +55,12 @@ import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.InstantType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.lang.NonNull;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
@ -80,19 +77,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
protected PlatformTransactionManager myPlatformTransactionManager;
@Autowired(required = false)
protected IFulltextSearchSvc mySearchDao;
@Autowired()
protected ISearchResultDao mySearchResultDao;
@Autowired
protected DaoConfig myDaoConfig;
@Autowired
private IResourceLinkDao myResourceLinkDao;
private String myResourceName;
private Class<T> myResourceType;
private String mySecondaryPrimaryKeyParamName;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private IReindexController myReindexController;
private MatchResourceUrlService myMatchResourceUrlService;
@Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
@ -279,7 +270,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public DeleteMethodOutcome deleteByUrl(String theUrl, List<DeleteConflict> deleteConflicts, RequestDetails theRequest) {
StopWatch w = new StopWatch();
Set<Long> resource = processMatchUrl(theUrl, myResourceType);
Set<Long> resource = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType);
if (resource.size() > 1) {
if (myDaoConfig.isAllowMultipleDelete() == false) {
throw new PreconditionFailedException(getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size()));
@ -379,7 +370,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity.setResourceType(toResourceName(theResource));
if (isNotBlank(theIfNoneExist)) {
Set<Long> match = processMatchUrl(theIfNoneExist, myResourceType);
Set<Long> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
throw new PreconditionFailedException(msg);
@ -390,12 +381,26 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
boolean serverAssignedId;
if (isNotBlank(theResource.getIdElement().getIdPart())) {
if (isValidPid(theResource.getIdElement())) {
throw new UnprocessableEntityException(
"This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
switch (myDaoConfig.getResourceClientIdStrategy()) {
case NOT_ALLOWED:
throw new ResourceNotFoundException(
getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedIdNotAllowed", theResource.getIdElement().getIdPart()));
case ALPHANUMERIC:
if (theResource.getIdElement().isIdPartValidLong()) {
throw new InvalidRequestException(
getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart()));
}
createForcedIdIfNeeded(entity, theResource.getIdElement(), false);
break;
case ANY:
createForcedIdIfNeeded(entity, theResource.getIdElement(), true);
break;
}
createForcedIdIfNeeded(entity, theResource.getIdElement());
serverAssignedId = false;
} else {
serverAssignedId = true;
}
// Notify interceptors
@ -416,8 +421,21 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Perform actual DB update
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, thePerformIndexing, theUpdateTime, false, thePerformIndexing);
theResource.setId(entity.getIdDt());
theResource.setId(entity.getIdDt());
if (serverAssignedId) {
switch (myDaoConfig.getResourceClientIdStrategy()) {
case NOT_ALLOWED:
case ALPHANUMERIC:
break;
case ANY:
ForcedId forcedId = createForcedIdIfNeeded(updatedEntity, theResource.getIdElement(), true);
if (forcedId != null) {
myForcedIdDao.save(forcedId);
}
break;
}
}
/*
* If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
@ -618,28 +636,27 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
if (myDaoConfig.isMarkResourcesForReindexingUponSearchParameterChange()) {
if (isNotBlank(theExpression)) {
if (isNotBlank(theExpression) && theExpression.contains(".")) {
final String resourceType = theExpression.substring(0, theExpression.indexOf('.'));
ourLog.debug("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", resourceType, theExpression);
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
Integer updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public @NonNull
Integer doInTransaction(@Nonnull TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
}
txTemplate.execute(t->{
myResourceReindexingSvc.markAllResourcesForReindexing(resourceType);
return null;
});
ourLog.debug("Marked {} resources for reindexing", updatedCount);
ourLog.debug("Marked resources of type {} for reindexing", resourceType);
}
}
mySearchParamRegistry.requestRefresh();
myReindexController.requestReindex();
}
@Autowired
private IResourceReindexingSvc myResourceReindexingSvc;
@Override
public <MT extends IBaseMetaType> MT metaAddOperation(IIdType theResourceId, MT theMetaAdd, RequestDetails theRequestDetails) {
// Notify interceptors
@ -727,6 +744,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return retVal;
}
@SuppressWarnings("JpaQlInspection")
@Override
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails) {
// Notify interceptors
@ -773,7 +791,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
myResourceName = def.getName();
if (mySecondaryPrimaryKeyParamName != null) {
RuntimeSearchParam sp = getSearchParamByName(def, mySecondaryPrimaryKeyParamName);
RuntimeSearchParam sp = mySearchParamRegistry.getSearchParamByName(def, mySecondaryPrimaryKeyParamName);
if (sp == null) {
throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]");
}
@ -829,7 +847,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public Set<Long> processMatchUrl(String theMatchUrl) {
return processMatchUrl(theMatchUrl, getResourceType());
return myMatchResourceUrlService.processMatchUrl(theMatchUrl, getResourceType());
}
@Override
@ -858,6 +876,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public T read(IIdType theId, RequestDetails theRequestDetails) {
return read(theId, theRequestDetails, false);
}
@Override
public T read(IIdType theId, RequestDetails theRequestDetails, boolean theDeletedOk) {
validateResourceTypeAndThrowIllegalArgumentException(theId);
// Notify interceptors
@ -873,10 +896,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
T retVal = toResource(myResourceType, entity, null, false);
if (entity.getDeleted() != null) {
throw new ResourceGoneException("Resource was deleted at " + new InstantType(entity.getDeleted()).getValueAsString());
if (theDeletedOk == false) {
if (entity.getDeleted() != null) {
throw new ResourceGoneException("Resource was deleted at " + new InstantType(entity.getDeleted()).getValueAsString());
}
}
ourLog.debug("Processed read on {} in {}ms", theId.getValue(), w.getMillisAndRestart());
return retVal;
}
@ -891,7 +917,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId) {
validateResourceTypeAndThrowIllegalArgumentException(theId);
Long pid = translateForcedIdToPid(getResourceName(), theId.getIdPart());
Long pid = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart());
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid);
if (entity == null) {
@ -931,7 +957,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
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) {
throw new ResourceNotFoundException(theId);
}
@ -1172,7 +1198,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Should not be null since the check above would have caught it
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceName);
RuntimeSearchParam paramDef = getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
for (String nextValue : theSource.get(nextParamName)) {
if (isNotBlank(nextValue)) {
@ -1212,7 +1238,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IIdType resourceId;
if (isNotBlank(theMatchUrl)) {
StopWatch sw = new StopWatch();
Set<Long> match = processMatchUrl(theMatchUrl, myResourceType);
Set<Long> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size());
throw new PreconditionFailedException(msg);
@ -1234,10 +1260,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
try {
entity = readEntityLatestVersion(resourceId);
} catch (ResourceNotFoundException e) {
if (resourceId.isIdPartValidLong()) {
throw new InvalidRequestException(
getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart()));
}
return doCreate(theResource, null, thePerformIndexing, new Date(), theRequestDetails);
}
}
@ -1253,6 +1275,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IBaseResource oldResource = toResource(entity, false);
/*
* Mark the entity as not deleted - This is also done in the actual updateInternal()
* method later on so it usually doesn't matter whether we do it here, but in the
* case of a transaction with multiple PUTs we don't get there until later so
* having this here means that a transaction can have a reference in one
* resource to another resource in the same transaction that is being
* un-deleted by the transaction. Wacky use case, sure. But it's real.
*
* See SystemProviderR4Test#testTransactionReSavesPreviouslyDeletedResources
* for a test that needs this.
*/
entity.setDeleted(null);
/*
* If we aren't indexing, that means we're doing this inside a transaction.
* The transaction will do the actual storage to the database a bit later on,
@ -1315,12 +1350,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private void validateGivenIdIsAppropriateToRetrieveResource(IIdType theId, BaseHasResource entity) {
if (entity.getForcedId() != null) {
if (theId.isIdPartValidLong()) {
// This means that the resource with the given numeric ID exists, but it has a "forced ID", meaning that
// as far as the outside world is concerned, the given ID doesn't exist (it's just an internal pointer
// to the
// forced ID)
throw new ResourceNotFoundException(theId);
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
if (theId.isIdPartValidLong()) {
// This means that the resource with the given numeric ID exists, but it has a "forced ID", meaning that
// as far as the outside world is concerned, the given ID doesn't exist (it's just an internal pointer
// to the
// forced ID)
throw new ResourceNotFoundException(theId);
}
}
}
}

View File

@ -1,44 +1,29 @@
package ca.uhn.fhir.jpa.dao;
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.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.jpa.util.ReindexFailureException;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.StopWatch;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.hibernate.search.util.impl.Executors;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.PageRequest;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.Query;
import java.util.*;
import java.util.concurrent.*;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import static org.apache.commons.lang3.StringUtils.isBlank;
/*
* #%L
* HAPI FHIR JPA Server
@ -65,85 +50,9 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
@Autowired
@Qualifier("myResourceCountsCache")
public ResourceCountCache myResourceCountsCache;
@Autowired
private IForcedIdDao myForcedIdDao;
private ReentrantLock myReindexLock = new ReentrantLock(false);
@Autowired
private ITermConceptDao myTermConceptDao;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private PlatformTransactionManager myTxManager;
@Autowired
private IResourceTableDao myResourceTableDao;
private ThreadFactory myReindexingThreadFactory = new BasicThreadFactory.Builder().namingPattern("ResourceReindex-%d").build();
private int doPerformReindexingPass(final Integer theCount) {
/*
* If any search parameters have been recently added or changed,
* this makes sure that the cache has been reloaded to reflect
* them.
*/
mySearchParamRegistry.refreshCacheIfNecessary();
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
return doPerformReindexingPassForResources(theCount, txTemplate);
}
@SuppressWarnings("ConstantConditions")
private int doPerformReindexingPassForResources(final Integer theCount, TransactionTemplate txTemplate) {
// Determine the IDs needing reindexing
List<Long> idsToReindex = txTemplate.execute(theStatus -> {
int maxResult = 500;
if (theCount != null) {
maxResult = Math.min(theCount, 2000);
}
maxResult = Math.max(maxResult, 10);
ourLog.debug("Beginning indexing query with maximum {}", maxResult);
return myResourceTableDao
.findIdsOfResourcesRequiringReindexing(new PageRequest(0, maxResult))
.getContent();
});
// If no IDs need reindexing, we're good here
if (idsToReindex.isEmpty()) {
return 0;
}
// Reindex
StopWatch sw = new StopWatch();
// Execute each reindex in a task within a threadpool
int threadCount = getConfig().getReindexThreadCount();
RejectedExecutionHandler rejectHandler = new Executors.BlockPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(threadCount, threadCount,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
myReindexingThreadFactory,
rejectHandler
);
List<Future<?>> futures = new ArrayList<>();
for (Long nextId : idsToReindex) {
futures.add(executor.submit(new ResourceReindexingTask(nextId)));
}
for (Future<?> next : futures) {
try {
next.get();
} catch (Exception e) {
throw new InternalErrorException("Failed to reindex: ", e);
}
}
executor.shutdown();
ourLog.info("Reindexed {} resources in {} threads - {}ms/resource", idsToReindex.size(), threadCount, sw.getMillisPerOperation(idsToReindex.size()));
return idsToReindex.size();
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
@Transactional(propagation = Propagation.NEVER)
public ExpungeOutcome expunge(ExpungeOptions theExpungeOptions) {
return doExpunge(null, null, null, theExpungeOptions);
}
@ -182,165 +91,5 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
return retVal;
}
@Transactional()
@Override
public int markAllResourcesForReindexing() {
ourLog.info("Marking all resources as needing reindexing");
int retVal = myEntityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " t SET t.myIndexStatus = null").executeUpdate();
ourLog.info("Marking all concepts as needing reindexing");
retVal += myTermConceptDao.markAllForReindexing();
ourLog.info("Done marking reindexing");
return retVal;
}
private void markResourceAsIndexingFailed(final long theId) {
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(@Nonnull TransactionStatus theStatus) {
ourLog.info("Marking resource with PID {} as indexing_failed", new Object[] {theId});
Query q = myEntityManager.createQuery("UPDATE ResourceTable t SET t.myIndexStatus = :status WHERE t.myId = :id");
q.setParameter("status", INDEX_STATUS_INDEXING_FAILED);
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceTag t WHERE t.myResourceId = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamCoords t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamDate t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamNumber t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamQuantity t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamString t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamToken t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamUri t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceLink t WHERE t.mySourceResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceLink t WHERE t.myTargetResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
return null;
}
});
}
@Override
@Transactional(propagation = Propagation.NEVER)
public Integer performReindexingPass(final Integer theCount) {
if (getConfig().isStatusBasedReindexingDisabled()) {
return -1;
}
if (!myReindexLock.tryLock()) {
return -1;
}
try {
return doPerformReindexingPass(theCount);
} catch (ReindexFailureException e) {
ourLog.warn("Reindexing failed for resource {}", e.getResourceId());
markResourceAsIndexingFailed(e.getResourceId());
return -1;
} finally {
myReindexLock.unlock();
}
}
private class ResourceReindexingTask implements Runnable {
private final Long myNextId;
public ResourceReindexingTask(Long theNextId) {
myNextId = theNextId;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.afterPropertiesSet();
Throwable reindexFailure;
try {
reindexFailure = txTemplate.execute(new TransactionCallback<Throwable>() {
@Override
public Throwable doInTransaction(TransactionStatus theStatus) {
ResourceTable resourceTable = myResourceTableDao.findById(myNextId).orElseThrow(IllegalStateException::new);
try {
/*
* This part is because from HAPI 1.5 - 1.6 we changed the format of forced ID to be "type/id" instead of just "id"
*/
ForcedId forcedId = resourceTable.getForcedId();
if (forcedId != null) {
if (isBlank(forcedId.getResourceType())) {
ourLog.info("Updating resource {} forcedId type to {}", forcedId.getForcedId(), resourceTable.getResourceType());
forcedId.setResourceType(resourceTable.getResourceType());
myForcedIdDao.save(forcedId);
}
}
final IBaseResource resource = toResource(resourceTable, false);
Class<? extends IBaseResource> resourceClass = getContext().getResourceDefinition(resourceTable.getResourceType()).getImplementingClass();
@SuppressWarnings("rawtypes") final IFhirResourceDao dao = getDaoOrThrowException(resourceClass);
dao.reindex(resource, resourceTable);
return null;
} catch (Exception e) {
ourLog.error("Failed to index resource {}: {}", resourceTable.getIdDt(), e.toString(), e);
theStatus.setRollbackOnly();
return e;
}
}
});
} catch (ResourceVersionConflictException e) {
/*
* We reindex in multiple threads, so it's technically possible that two threads try
* to index resources that cause a constraint error now (i.e. because a unique index has been
* added that didn't previously exist). In this case, one of the threads would succeed and
* not get this error, so we'll let the other one fail and try
* again later.
*/
ourLog.info("Failed to reindex {} because of a version conflict. Leaving in unindexed state: {}", e.getMessage());
reindexFailure = null;
}
if (reindexFailure != null) {
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
ourLog.info("Setting resource PID[{}] status to ERRORED", myNextId);
myResourceTableDao.updateStatusToErrored(myNextId);
}
});
}
}
}
}

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import com.google.common.collect.Sets;
@ -36,21 +38,6 @@ import java.util.*;
public class DaoConfig {
/**
* Default {@link #getTreatReferencesAsLogical() logical URL bases}. Includes the following
* values:
* <ul>
* <li><code>"http://hl7.org/fhir/valueset-*"</code></li>
* <li><code>"http://hl7.org/fhir/codesystem-*"</code></li>
* <li><code>"http://hl7.org/fhir/StructureDefinition/*"</code></li>
* </ul>
*/
public static final Set<String> DEFAULT_LOGICAL_BASE_URLS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"http://hl7.org/fhir/ValueSet/*",
"http://hl7.org/fhir/CodeSystem/*",
"http://hl7.org/fhir/valueset-*",
"http://hl7.org/fhir/codesystem-*",
"http://hl7.org/fhir/StructureDefinition/*")));
/**
* Default value for {@link #setReuseCachedSearchResultsForMillis(Long)}: 60000ms (one minute)
*/
@ -86,24 +73,22 @@ public class DaoConfig {
)));
private static final Logger ourLog = LoggerFactory.getLogger(DaoConfig.class);
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
/**
* Child Configurations
*/
private ModelConfig myModelConfig = new ModelConfig();
/**
* update setter javadoc if default changes
*/
private Long myTranslationCachesExpireAfterWriteInMinutes = DEFAULT_TRANSLATION_CACHES_EXPIRE_AFTER_WRITE_IN_MINUTES;
/**
* update setter javadoc if default changes
*/
private boolean myAllowExternalReferences = false;
/**
* update setter javadoc if default changes
*/
private boolean myAllowContainsSearches = false;
/**
* update setter javadoc if default changes
*/
private boolean myAllowInlineMatchUrlReferences = true;
private boolean myAllowMultipleDelete;
private boolean myDefaultSearchParamsCanBeOverridden = false;
/**
* update setter javadoc if default changes
*/
@ -141,8 +126,6 @@ public class DaoConfig {
private Long myReuseCachedSearchResultsForMillis = DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS;
private boolean mySchedulingDisabled;
private boolean mySuppressUpdatesWithNoChange = true;
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<>();
private Set<String> myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS);
private boolean myAutoCreatePlaceholderReferenceTargets;
private Integer myCacheControlNoStoreMaxResultsUpperLimit = 1000;
private Integer myCountSearchResultsUpTo = null;
@ -156,6 +139,8 @@ public class DaoConfig {
private List<Integer> mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1);
private List<WarmCacheEntry> myWarmCacheEntries = new ArrayList<>();
private boolean myDisableHashBasedSearches;
private boolean myEnableInMemorySubscriptionMatching = true;
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
/**
* Constructor
@ -222,12 +207,7 @@ public class DaoConfig {
* @see #setTreatReferencesAsLogical(Set)
*/
public void addTreatReferencesAsLogical(String theTreatReferencesAsLogical) {
validateTreatBaseUrlsAsLocal(theTreatReferencesAsLogical);
if (myTreatReferencesAsLogical == null) {
myTreatReferencesAsLogical = new HashSet<>();
}
myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
myModelConfig.addTreatReferencesAsLogical(theTreatReferencesAsLogical);
}
/**
@ -642,9 +622,39 @@ public class DaoConfig {
myResourceMetaCountHardLimit = theResourceMetaCountHardLimit;
}
/**
* Controls the behaviour when a client-assigned ID is encountered, i.e. an HTTP PUT
* on a resource ID that does not already exist in the database.
* <p>
* Default is {@link ClientIdStrategyEnum#ALPHANUMERIC}
* </p>
*/
public ClientIdStrategyEnum getResourceClientIdStrategy() {
return myResourceClientIdStrategy;
}
/**
* Controls the behaviour when a client-assigned ID is encountered, i.e. an HTTP PUT
* on a resource ID that does not already exist in the database.
* <p>
* Default is {@link ClientIdStrategyEnum#ALPHANUMERIC}
* </p>
*
* @param theResourceClientIdStrategy Must not be <code>null</code>
*/
public void setResourceClientIdStrategy(ClientIdStrategyEnum theResourceClientIdStrategy) {
Validate.notNull(theResourceClientIdStrategy, "theClientIdStrategy must not be null");
myResourceClientIdStrategy = theResourceClientIdStrategy;
}
/**
* This setting configures the strategy to use in generating IDs for newly
* created resources on the server. The default is {@link IdStrategyEnum#SEQUENTIAL_NUMERIC}.
* <p>
* This strategy is only used for server-assigned IDs, i.e. for HTTP POST
* where the client is requesing that the server store a new resource and give
* it an ID.
* </p>
*/
public IdStrategyEnum getResourceServerIdStrategy() {
return myResourceServerIdStrategy;
@ -653,8 +663,13 @@ public class DaoConfig {
/**
* This setting configures the strategy to use in generating IDs for newly
* created resources on the server. The default is {@link IdStrategyEnum#SEQUENTIAL_NUMERIC}.
* <p>
* This strategy is only used for server-assigned IDs, i.e. for HTTP POST
* where the client is requesing that the server store a new resource and give
* it an ID.
* </p>
*
* @param theResourceIdStrategy The strategy. Must not be null.
* @param theResourceIdStrategy The strategy. Must not be <code>null</code>.
*/
public void setResourceServerIdStrategy(IdStrategyEnum theResourceIdStrategy) {
Validate.notNull(theResourceIdStrategy, "theResourceIdStrategy must not be null");
@ -717,57 +732,6 @@ public class DaoConfig {
myTranslationCachesExpireAfterWriteInMinutes = translationCachesExpireAfterWriteInMinutes;
}
/**
* This setting may be used to advise the server that any references found in
* resources that have any of the base URLs given here will be replaced with
* simple local references.
* <p>
* For example, if the set contains the value <code>http://example.com/base/</code>
* and a resource is submitted to the server that contains a reference to
* <code>http://example.com/base/Patient/1</code>, the server will automatically
* convert this reference to <code>Patient/1</code>
* </p>
* <p>
* Note that this property has different behaviour from {@link DaoConfig#getTreatReferencesAsLogical()}
* </p>
*
* @see #getTreatReferencesAsLogical()
*/
public Set<String> getTreatBaseUrlsAsLocal() {
return myTreatBaseUrlsAsLocal;
}
/**
* This setting may be used to advise the server that any references found in
* resources that have any of the base URLs given here will be replaced with
* simple local references.
* <p>
* For example, if the set contains the value <code>http://example.com/base/</code>
* and a resource is submitted to the server that contains a reference to
* <code>http://example.com/base/Patient/1</code>, the server will automatically
* convert this reference to <code>Patient/1</code>
* </p>
*
* @param theTreatBaseUrlsAsLocal The set of base URLs. May be <code>null</code>, which
* means no references will be treated as external
*/
public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) {
if (theTreatBaseUrlsAsLocal != null) {
for (String next : theTreatBaseUrlsAsLocal) {
validateTreatBaseUrlsAsLocal(next);
}
}
HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>();
for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
while (next.endsWith("/")) {
next = next.substring(0, next.length() - 1);
}
treatBaseUrlsAsLocal.add(next);
}
myTreatBaseUrlsAsLocal = treatBaseUrlsAsLocal;
}
/**
* This setting may be used to advise the server that any references found in
* resources that have any of the base URLs given here will be treated as logical
@ -787,10 +751,10 @@ public class DaoConfig {
* <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
* </ul>
*
* @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property
* @see ModelConfig#DEFAULT_LOGICAL_BASE_URLS Default values for this property
*/
public Set<String> getTreatReferencesAsLogical() {
return myTreatReferencesAsLogical;
return myModelConfig.getTreatReferencesAsLogical();
}
/**
@ -812,49 +776,13 @@ public class DaoConfig {
* <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
* </ul>
*
* @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property
* @see ModelConfig#DEFAULT_LOGICAL_BASE_URLS Default values for this property
*/
public DaoConfig setTreatReferencesAsLogical(Set<String> theTreatReferencesAsLogical) {
myTreatReferencesAsLogical = theTreatReferencesAsLogical;
myModelConfig.setTreatReferencesAsLogical(theTreatReferencesAsLogical);
return this;
}
/**
* If enabled, the server will support the use of :contains searches,
* which are helpful but can have adverse effects on performance.
* <p>
* Default is <code>false</code> (Note that prior to HAPI FHIR
* 3.5.0 the default was <code>true</code>)
* </p>
* <p>
* Note: If you change this value after data already has
* already been stored in the database, you must for a reindexing
* of all data in the database or resources may not be
* searchable.
* </p>
*/
public boolean isAllowContainsSearches() {
return myAllowContainsSearches;
}
/**
* If enabled, the server will support the use of :contains searches,
* which are helpful but can have adverse effects on performance.
* <p>
* Default is <code>false</code> (Note that prior to HAPI FHIR
* 3.5.0 the default was <code>true</code>)
* </p>
* <p>
* Note: If you change this value after data already has
* already been stored in the database, you must for a reindexing
* of all data in the database or resources may not be
* searchable.
* </p>
*/
public void setAllowContainsSearches(boolean theAllowContainsSearches) {
this.myAllowContainsSearches = theAllowContainsSearches;
}
/**
* If set to <code>true</code> (default is <code>false</code>) the server will allow
* resources to have references to external servers. For example if this server is
@ -881,7 +809,7 @@ public class DaoConfig {
* @see #setAllowExternalReferences(boolean)
*/
public boolean isAllowExternalReferences() {
return myAllowExternalReferences;
return myModelConfig.isAllowExternalReferences();
}
/**
@ -910,7 +838,7 @@ public class DaoConfig {
* @see #setAllowExternalReferences(boolean)
*/
public void setAllowExternalReferences(boolean theAllowExternalReferences) {
myAllowExternalReferences = theAllowExternalReferences;
myModelConfig.setAllowExternalReferences(theAllowExternalReferences);
}
/**
@ -987,38 +915,6 @@ public class DaoConfig {
myAutoCreatePlaceholderReferenceTargets = theAutoCreatePlaceholderReferenceTargets;
}
/**
* If set to {@code true} the default search params (i.e. the search parameters that are
* defined by the FHIR specification itself) may be overridden by uploading search
* parameters to the server with the same code as the built-in search parameter.
* <p>
* This can be useful if you want to be able to disable or alter
* the behaviour of the default search parameters.
* </p>
* <p>
* The default value for this setting is {@code false}
* </p>
*/
public boolean isDefaultSearchParamsCanBeOverridden() {
return myDefaultSearchParamsCanBeOverridden;
}
/**
* If set to {@code true} the default search params (i.e. the search parameters that are
* defined by the FHIR specification itself) may be overridden by uploading search
* parameters to the server with the same code as the built-in search parameter.
* <p>
* This can be useful if you want to be able to disable or alter
* the behaviour of the default search parameters.
* </p>
* <p>
* The default value for this setting is {@code false}
* </p>
*/
public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) {
myDefaultSearchParamsCanBeOverridden = theDefaultSearchParamsCanBeOverridden;
}
/**
* If set to <code>false</code> (default is <code>true</code>) resources will be permitted to be
* deleted even if other resources currently contain references to them.
@ -1236,7 +1132,7 @@ public class DaoConfig {
/**
* If set to <code>true</code> (default is <code>true</code>), indexes will be
* created for search parameters marked as {@link JpaConstants#EXT_SP_UNIQUE}.
* created for search parameters marked as {@link SearchParamConstants#EXT_SP_UNIQUE}.
* This is a HAPI FHIR specific extension which can be used to specify that no more than one
* resource can exist which matches a given criteria, using a database constraint to
* enforce this.
@ -1247,7 +1143,7 @@ public class DaoConfig {
/**
* If set to <code>true</code> (default is <code>true</code>), indexes will be
* created for search parameters marked as {@link JpaConstants#EXT_SP_UNIQUE}.
* created for search parameters marked as {@link SearchParamConstants#EXT_SP_UNIQUE}.
* This is a HAPI FHIR specific extension which can be used to specify that no more than one
* resource can exist which matches a given criteria, using a database constraint to
* enforce this.
@ -1353,18 +1249,8 @@ public class DaoConfig {
* given number.
* </p>
*/
public void setSearchPreFetchThresholds(List<Integer> thePreFetchThresholds) {
Validate.isTrue(thePreFetchThresholds.size() > 0, "thePreFetchThresholds must not be empty");
int last = 0;
for (Integer nextInteger : thePreFetchThresholds) {
int nextInt = nextInteger.intValue();
Validate.isTrue(nextInt > 0 || nextInt == -1, nextInt + " is not a valid prefetch threshold");
Validate.isTrue(nextInt != last, "Prefetch thresholds must be sequential");
Validate.isTrue(nextInt > last || nextInt == -1, "Prefetch thresholds must be sequential");
Validate.isTrue(last != -1, "Prefetch thresholds must be sequential");
last = nextInt;
}
mySearchPreFetchThresholds = thePreFetchThresholds;
public List<Integer> getSearchPreFetchThresholds() {
return mySearchPreFetchThresholds;
}
/**
@ -1380,8 +1266,18 @@ public class DaoConfig {
* given number.
* </p>
*/
public List<Integer> getSearchPreFetchThresholds() {
return mySearchPreFetchThresholds;
public void setSearchPreFetchThresholds(List<Integer> thePreFetchThresholds) {
Validate.isTrue(thePreFetchThresholds.size() > 0, "thePreFetchThresholds must not be empty");
int last = 0;
for (Integer nextInteger : thePreFetchThresholds) {
int nextInt = nextInteger.intValue();
Validate.isTrue(nextInt > 0 || nextInt == -1, nextInt + " is not a valid prefetch threshold");
Validate.isTrue(nextInt != last, "Prefetch thresholds must be sequential");
Validate.isTrue(nextInt > last || nextInt == -1, "Prefetch thresholds must be sequential");
Validate.isTrue(last != -1, "Prefetch thresholds must be sequential");
last = nextInt;
}
mySearchPreFetchThresholds = thePreFetchThresholds;
}
/**
@ -1412,6 +1308,161 @@ public class DaoConfig {
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 ModelConfig getModelConfig() {
return myModelConfig;
}
/**
* If enabled, the server will support the use of :contains searches,
* which are helpful but can have adverse effects on performance.
* <p>
* Default is <code>false</code> (Note that prior to HAPI FHIR
* 3.5.0 the default was <code>true</code>)
* </p>
* <p>
* Note: If you change this value after data already has
* already been stored in the database, you must for a reindexing
* of all data in the database or resources may not be
* searchable.
* </p>
*/
public boolean isAllowContainsSearches() {
return this.myModelConfig.isAllowContainsSearches();
}
/**
* If enabled, the server will support the use of :contains searches,
* which are helpful but can have adverse effects on performance.
* <p>
* Default is <code>false</code> (Note that prior to HAPI FHIR
* 3.5.0 the default was <code>true</code>)
* </p>
* <p>
* Note: If you change this value after data already has
* already been stored in the database, you must for a reindexing
* of all data in the database or resources may not be
* searchable.
* </p>
*/
public void setAllowContainsSearches(boolean theAllowContainsSearches) {
this.myModelConfig.setAllowContainsSearches(theAllowContainsSearches);
}
/**
* This setting may be used to advise the server that any references found in
* resources that have any of the base URLs given here will be replaced with
* simple local references.
* <p>
* For example, if the set contains the value <code>http://example.com/base/</code>
* and a resource is submitted to the server that contains a reference to
* <code>http://example.com/base/Patient/1</code>, the server will automatically
* convert this reference to <code>Patient/1</code>
* </p>
* <p>
* Note that this property has different behaviour from {@link DaoConfig#getTreatReferencesAsLogical()}
* </p>
*
* @see #getTreatReferencesAsLogical()
*/
public Set<String> getTreatBaseUrlsAsLocal() {
return myModelConfig.getTreatBaseUrlsAsLocal();
}
/**
* This setting may be used to advise the server that any references found in
* resources that have any of the base URLs given here will be replaced with
* simple local references.
* <p>
* For example, if the set contains the value <code>http://example.com/base/</code>
* and a resource is submitted to the server that contains a reference to
* <code>http://example.com/base/Patient/1</code>, the server will automatically
* convert this reference to <code>Patient/1</code>
* </p>
*
* @param theTreatBaseUrlsAsLocal The set of base URLs. May be <code>null</code>, which
* means no references will be treated as external
*/
public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) {
myModelConfig.setTreatBaseUrlsAsLocal(theTreatBaseUrlsAsLocal);
}
/**
* If set to {@code true} the default search params (i.e. the search parameters that are
* defined by the FHIR specification itself) may be overridden by uploading search
* parameters to the server with the same code as the built-in search parameter.
* <p>
* This can be useful if you want to be able to disable or alter
* the behaviour of the default search parameters.
* </p>
* <p>
* The default value for this setting is {@code false}
* </p>
*/
public boolean isDefaultSearchParamsCanBeOverridden() {
return myModelConfig.isDefaultSearchParamsCanBeOverridden();
}
/**
* If set to {@code true} the default search params (i.e. the search parameters that are
* defined by the FHIR specification itself) may be overridden by uploading search
* parameters to the server with the same code as the built-in search parameter.
* <p>
* This can be useful if you want to be able to disable or alter
* the behaviour of the default search parameters.
* </p>
* <p>
* The default value for this setting is {@code false}
* </p>
*/
public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) {
myModelConfig.setDefaultSearchParamsCanBeOverridden(theDefaultSearchParamsCanBeOverridden);
}
public enum IndexEnabledEnum {
ENABLED,
DISABLED
@ -1429,16 +1480,33 @@ public class DaoConfig {
UUID
}
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");
public enum ClientIdStrategyEnum {
/**
* Clients are not allowed to supply IDs for resources that do not
* already exist
*/
NOT_ALLOWED,
int starIdx = theUrl.indexOf('*');
if (starIdx != -1) {
if (starIdx != theUrl.length() - 1) {
throw new IllegalArgumentException("Base URL wildcard character (*) can only appear at the end of the string: " + theUrl);
}
}
/**
* Clients may supply IDs but these IDs are not permitted to be purely
* numeric. In other words, values such as "A", "A1" and "000A" would be considered
* valid but "123" would not.
* <p><b>This is the default setting.</b></p>
*/
ALPHANUMERIC,
/**
* Clients may supply any ID including purely numeric IDs. Note that this setting should
* only be set on an empty database, or on a database that has always had this setting
* set as it causes a "forced ID" to be used for all resources.
* <p>
* Note that if you use this setting, it is highly recommended that you also
* set the {@link #setResourceServerIdStrategy(IdStrategyEnum) ResourceServerIdStrategy}
* to {@link IdStrategyEnum#UUID} in order to avoid any potential for conflicts. Otherwise
* a database sequence will be used to generate IDs and these IDs can conflict with
* client-assigned numeric IDs.
* </P>
*/
ANY
}
}

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;

View File

@ -22,20 +22,29 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
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.stereotype.Component;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component("myDaoRegistry")
public class DaoRegistry implements ApplicationContextAware {
private ApplicationContext myAppCtx;
@Autowired
private FhirContext myCtx;
private FhirContext myContext;
private volatile Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao;
private volatile IFhirSystemDao<?, ?> mySystemDao;
@ -44,8 +53,8 @@ public class DaoRegistry implements ApplicationContextAware {
myAppCtx = theApplicationContext;
}
public IFhirSystemDao<?, ?> getSystemDao() {
IFhirSystemDao<?, ?> retVal = mySystemDao;
public IFhirSystemDao getSystemDao() {
IFhirSystemDao retVal = mySystemDao;
if (retVal == null) {
retVal = myAppCtx.getBean(IFhirSystemDao.class);
mySystemDao = retVal;
@ -53,25 +62,70 @@ public class DaoRegistry implements ApplicationContextAware {
return retVal;
}
public IFhirResourceDao<?> getResourceDao(String theResourceName) {
IFhirResourceDao<?> retVal = getResourceNameToResourceDao().get(theResourceName);
Validate.notNull(retVal, "No DAO exists for resource type %s - Have: %s", theResourceName, myResourceNameToResourceDao);
return retVal;
}
private Map<String, IFhirResourceDao<?>> getResourceNameToResourceDao() {
Map<String, IFhirResourceDao<?>> retVal = myResourceNameToResourceDao;
if (retVal == null || retVal.isEmpty()) {
retVal = new HashMap<>();
Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
for (IFhirResourceDao nextResourceDao : resourceDaos.values()) {
RuntimeResourceDefinition nextResourceDef = myCtx.getResourceDefinition(nextResourceDao.getResourceType());
retVal.put(nextResourceDef.getName(), nextResourceDao);
}
myResourceNameToResourceDao = retVal;
public IFhirResourceDao getResourceDao(String theResourceName) {
init();
IFhirResourceDao retVal = myResourceNameToResourceDao.get(theResourceName);
if (retVal == null) {
List<String> supportedResourceTypes = myResourceNameToResourceDao
.keySet()
.stream()
.sorted()
.collect(Collectors.toList());
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + theResourceName + " - Can handle: " + supportedResourceTypes);
}
return retVal;
}
public <R extends IBaseResource> IFhirResourceDao<R> getResourceDao(Class<R> theResourceType) {
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);
}
private void init() {
if (myResourceNameToResourceDao != null && !myResourceNameToResourceDao.isEmpty()) {
return;
}
Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
initializeMaps(resourceDaos.values());
}
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;
}
public void setResourceDaos(Collection<IFhirResourceDao> theResourceDaos) {
initializeMaps(theResourceDaos);
}
public IFhirResourceDao getSubscriptionDao() {
return getResourceDao(ResourceTypeEnum.SUBSCRIPTION.getCode());
}
}

View File

@ -0,0 +1,53 @@
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.jpa.searchparam.registry.BaseSearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
public class DatabaseSearchParamProvider implements ISearchParamProvider {
@Autowired
private PlatformTransactionManager myTxManager;
@Autowired
private DaoRegistry myDaoRegistry;
@Override
public IBundleProvider search(SearchParameterMap theParams) {
return myDaoRegistry.getResourceDao(ResourceTypeEnum.SEARCHPARAMETER.getCode()).search(theParams);
}
@Override
public <SP extends IBaseResource> void refreshCache(BaseSearchParamRegistry<SP> theSearchParamRegistry, long theRefreshInterval) {
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.execute(t->{
theSearchParamRegistry.doRefresh(theRefreshInterval);
return null;
});
}
}

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.dao;
import java.util.List;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.MethodOutcome;
/**

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
class EncodedResource {

View File

@ -11,7 +11,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;

View File

@ -24,10 +24,11 @@ import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Encounter;
import ca.uhn.fhir.rest.api.SortSpec;

View File

@ -24,10 +24,11 @@ import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.api.*;

View File

@ -32,7 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.SearchParameter;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;

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