diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml
index 2c010d73a21..da34ff1770e 100644
--- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml
+++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml
@@ -7,7 +7,7 @@
hapi-fhir-serviceloaders
ca.uhn.hapi.fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../pom.xml
diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml
index 8e806a73771..3e09e7c0160 100644
--- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml
+++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml
@@ -7,7 +7,7 @@
hapi-fhir
ca.uhn.hapi.fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../../pom.xml
diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml
index 722bcfa2efe..3a7bdba7d54 100644
--- a/hapi-fhir-serviceloaders/pom.xml
+++ b/hapi-fhir-serviceloaders/pom.xml
@@ -5,7 +5,7 @@
hapi-deployable-pom
ca.uhn.hapi.fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
index 924a9753c59..93b126309c1 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
index ee838830f12..7a250b4b4c5 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir-spring-boot-samples
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
hapi-fhir-spring-boot-sample-client-apache
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
index 1311703ec16..a99bea8806d 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir-spring-boot-samples
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
index bc4fa610eb4..e3b0cf47a83 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir-spring-boot-samples
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
index 2c2fe2f31cb..f45a5e76eba 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir-spring-boot
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
index 4aedd388b8b..d554053161d 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml
index d9ede8644f7..8b56e169941 100644
--- a/hapi-fhir-spring-boot/pom.xml
+++ b/hapi-fhir-spring-boot/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../pom.xml
diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml
index f7a3ca491fc..0827041f3d5 100644
--- a/hapi-fhir-sql-migrate/pom.xml
+++ b/hapi-fhir-sql-migrate/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
index b8db102a469..75767ba6bf3 100644
--- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
+++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
@@ -207,14 +207,7 @@ public class JdbcUtils {
case Types.BLOB:
return new ColumnType(ColumnTypeEnum.BLOB, length);
case Types.LONGVARBINARY:
- if (DriverTypeEnum.MYSQL_5_7.equals(theConnectionProperties.getDriverType())) {
- // See git
- return new ColumnType(ColumnTypeEnum.BLOB, length);
- } else {
- throw new IllegalArgumentException(
- Msg.code(32) + "Don't know how to handle datatype " + dataType
- + " for column " + theColumnName + " on table " + theTableName);
- }
+ return new ColumnType(ColumnTypeEnum.BINARY, length);
case Types.VARBINARY:
if (DriverTypeEnum.MSSQL_2012.equals(theConnectionProperties.getDriverType())) {
// MS SQLServer seems to be mapping BLOB to VARBINARY under the covers, so we need
diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeEnum.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeEnum.java
index 1de4092beba..acebbb82383 100644
--- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeEnum.java
+++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeEnum.java
@@ -37,5 +37,7 @@ public enum ColumnTypeEnum {
* @Column(length=Integer.MAX_VALUE)
*/
TEXT,
+ /** Long inline binary */
+ BINARY,
BIG_DECIMAL;
}
diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeToDriverTypeToSqlType.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeToDriverTypeToSqlType.java
index f483b74b330..7592d6c5d1a 100644
--- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeToDriverTypeToSqlType.java
+++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeToDriverTypeToSqlType.java
@@ -131,6 +131,14 @@ public final class ColumnTypeToDriverTypeToSqlType {
setColumnType(ColumnTypeEnum.TEXT, DriverTypeEnum.POSTGRES_9_4, "text");
setColumnType(ColumnTypeEnum.TEXT, DriverTypeEnum.MSSQL_2012, "varchar(MAX)");
+ setColumnType(ColumnTypeEnum.BINARY, DriverTypeEnum.H2_EMBEDDED, "blob");
+ setColumnType(ColumnTypeEnum.BINARY, DriverTypeEnum.DERBY_EMBEDDED, "blob");
+ setColumnType(ColumnTypeEnum.BINARY, DriverTypeEnum.MARIADB_10_1, "longblob");
+ setColumnType(ColumnTypeEnum.BINARY, DriverTypeEnum.MYSQL_5_7, "longblob");
+ setColumnType(ColumnTypeEnum.BINARY, DriverTypeEnum.ORACLE_12C, "blob");
+ setColumnType(ColumnTypeEnum.BINARY, DriverTypeEnum.POSTGRES_9_4, "bytea");
+ setColumnType(ColumnTypeEnum.BINARY, DriverTypeEnum.MSSQL_2012, "varbinary(MAX)");
+
setColumnType(ColumnTypeEnum.BIG_DECIMAL, DriverTypeEnum.H2_EMBEDDED, "numeric(38,2)");
setColumnType(ColumnTypeEnum.BIG_DECIMAL, DriverTypeEnum.DERBY_EMBEDDED, "decimal(31,2)");
setColumnType(ColumnTypeEnum.BIG_DECIMAL, DriverTypeEnum.MARIADB_10_1, "decimal(38,2)");
diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java
index 284e24ac3c3..70dc3d2c0d1 100644
--- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java
+++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java
@@ -133,9 +133,10 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask {
}
break;
case ORACLE_12C:
- String oracleNullableStmt = !alreadyCorrectNullable ? notNull : "";
- sql = "alter table " + getTableName() + " modify ( " + getColumnName() + " " + type + oracleNullableStmt
- + " )";
+ String oracleNullableStmt = alreadyCorrectNullable ? "" : notNull;
+ String oracleTypeStmt = alreadyOfCorrectType ? "" : type;
+ sql = "alter table " + getTableName() + " modify ( " + getColumnName() + " " + oracleTypeStmt + " "
+ + oracleNullableStmt + " )";
break;
case MSSQL_2012:
sql = "alter table " + getTableName() + " alter column " + getColumnName() + " " + type + notNull;
diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml
index 2fabd9ec3d0..bb38f3ccee7 100644
--- a/hapi-fhir-storage-batch2-jobs/pom.xml
+++ b/hapi-fhir-storage-batch2-jobs/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkExportAppCtx.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkExportAppCtx.java
index b44bb04f8c0..56de694bc49 100644
--- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkExportAppCtx.java
+++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkExportAppCtx.java
@@ -36,6 +36,7 @@ import org.springframework.context.annotation.Scope;
public class BulkExportAppCtx {
public static final String WRITE_TO_BINARIES = "write-to-binaries";
+ public static final String CREATE_REPORT_STEP = "create-report-step";
@Bean
public JobDefinition bulkExportJobDefinition() {
@@ -65,7 +66,7 @@ public class BulkExportAppCtx {
writeBinaryStep())
// finalize the job (set to complete)
.addFinalReducerStep(
- "create-report-step",
+ CREATE_REPORT_STEP,
"Creates the output report from a bulk export job",
BulkExportJobResults.class,
createReportStep())
@@ -119,16 +120,25 @@ public class BulkExportAppCtx {
return new FetchResourceIdsStep();
}
+ /**
+ * Note, this bean is only used for version 1 of the bulk export job definition
+ */
@Bean
public ExpandResourcesStep expandResourcesStep() {
return new ExpandResourcesStep();
}
+ /**
+ * Note, this bean is only used for version 1 of the bulk export job definition
+ */
@Bean
public WriteBinaryStep writeBinaryStep() {
return new WriteBinaryStep();
}
+ /**
+ * Note, this bean is only used for version 2 of the bulk export job definition
+ */
@Bean
public ExpandResourceAndWriteBinaryStep expandResourceAndWriteBinaryStep() {
return new ExpandResourceAndWriteBinaryStep();
diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourceAndWriteBinaryStep.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourceAndWriteBinaryStep.java
index 6f43cc67967..6200ffdd8c0 100644
--- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourceAndWriteBinaryStep.java
+++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourceAndWriteBinaryStep.java
@@ -34,6 +34,7 @@ import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
+import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
@@ -41,7 +42,6 @@ import ca.uhn.fhir.jpa.api.model.PersistentIdToForcedIdMap;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.bulk.export.api.IBulkExportProcessor;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
-import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
@@ -75,11 +75,13 @@ import org.springframework.context.ApplicationContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
-import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import static ca.uhn.fhir.rest.api.Constants.PARAM_ID;
@@ -103,7 +105,7 @@ public class ExpandResourceAndWriteBinaryStep
private IBulkExportProcessor> myBulkExportProcessor;
@Autowired
- private StorageSettings myStorageSettings;
+ private JpaStorageSettings myStorageSettings;
@Autowired
private ApplicationContext myApplicationContext;
@@ -119,6 +121,23 @@ public class ExpandResourceAndWriteBinaryStep
private volatile ResponseTerminologyTranslationSvc myResponseTerminologyTranslationSvc;
+ /**
+ * Note on the design of this step:
+ * This step takes a list of resource PIDs as input, fetches those
+ * resources, applies a bunch of filtering/consent/MDM/etc. modifications
+ * on them, serializes the result as NDJSON files, and then persists those
+ * NDJSON files as Binary resources.
+ *
+ * We want to avoid writing files which exceed the configured maximum
+ * file size, and we also want to avoid keeping too much in memory
+ * at any given time, so this class works a bit like a stream processor
+ * (although not using Java streams).
+ *
+ * The {@link #fetchResourcesByIdAndConsumeThem(ResourceIdList, RequestPartitionId, Consumer)}
+ * method loads the resources by ID, {@link ExpandResourcesConsumer} handles
+ * the filtering and whatnot, then the {@link NdJsonResourceWriter}
+ * ultimately writes them.
+ */
@Nonnull
@Override
public RunOutcome run(
@@ -126,235 +145,36 @@ public class ExpandResourceAndWriteBinaryStep
@Nonnull IJobDataSink theDataSink)
throws JobExecutionFailedException {
- List expandedResourcesList = expandResourcesFromList(theStepExecutionDetails);
- int numResourcesProcessed = 0;
- ourLog.info("Write binary step of Job Export");
+ // Currently only NDJSON output format is supported, but we could add other
+ // kinds of writers here for other formats if needed
+ NdJsonResourceWriter resourceWriter = new NdJsonResourceWriter(theStepExecutionDetails, theDataSink);
- // write to binary each resource type separately, without chunking, we need to do this in a loop now
- for (ExpandedResourcesList expandedResources : expandedResourcesList) {
+ expandResourcesFromList(theStepExecutionDetails, resourceWriter);
- numResourcesProcessed += expandedResources.getStringifiedResources().size();
-
- ourLog.info("Writing {} resources to binary file", numResourcesProcessed);
-
- @SuppressWarnings("unchecked")
- IFhirResourceDao binaryDao = myDaoRegistry.getResourceDao("Binary");
-
- IBaseBinary binary = BinaryUtil.newBinary(myFhirContext);
-
- addMetadataExtensionsToBinary(theStepExecutionDetails, expandedResources, binary);
-
- // TODO
- // should be dependent on the output format in parameters but for now, only NDJSON is supported
- binary.setContentType(Constants.CT_FHIR_NDJSON);
-
- int processedRecordsCount = 0;
- try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
- try (OutputStreamWriter streamWriter = getStreamWriter(outputStream)) {
- for (String stringified : expandedResources.getStringifiedResources()) {
- streamWriter.append(stringified);
- streamWriter.append("\n");
- processedRecordsCount++;
- }
- streamWriter.flush();
- outputStream.flush();
- }
- binary.setContent(outputStream.toByteArray());
- } catch (IOException ex) {
- String errorMsg = String.format(
- "Failure to process resource of type %s : %s",
- expandedResources.getResourceType(), ex.getMessage());
- ourLog.error(errorMsg);
-
- throw new JobExecutionFailedException(Msg.code(2431) + errorMsg);
- }
-
- SystemRequestDetails srd = new SystemRequestDetails();
- BulkExportJobParameters jobParameters = theStepExecutionDetails.getParameters();
- RequestPartitionId partitionId = jobParameters.getPartitionId();
- if (partitionId == null) {
- srd.setRequestPartitionId(RequestPartitionId.defaultPartition());
- } else {
- srd.setRequestPartitionId(partitionId);
- }
-
- // Pick a unique ID and retry until we get one that isn't already used. This is just to
- // avoid any possibility of people guessing the IDs of these Binaries and fishing for them.
- while (true) {
- // Use a random ID to make it harder to guess IDs - 32 characters of a-zA-Z0-9
- // has 190 bts of entropy according to https://www.omnicalculator.com/other/password-entropy
- String proposedId = RandomTextUtils.newSecureRandomAlphaNumericString(32);
- binary.setId(proposedId);
-
- // Make sure we don't accidentally reuse an ID. This should be impossible given the
- // amount of entropy in the IDs but might as well be sure.
- try {
- IBaseBinary output = binaryDao.read(binary.getIdElement(), new SystemRequestDetails(), true);
- if (output != null) {
- continue;
- }
- } catch (ResourceNotFoundException e) {
- // good
- }
-
- break;
- }
-
- if (myFhirContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) {
- if (isNotBlank(jobParameters.getBinarySecurityContextIdentifierSystem())
- || isNotBlank(jobParameters.getBinarySecurityContextIdentifierValue())) {
- FhirTerser terser = myFhirContext.newTerser();
- terser.setElement(
- binary,
- "securityContext.identifier.system",
- jobParameters.getBinarySecurityContextIdentifierSystem());
- terser.setElement(
- binary,
- "securityContext.identifier.value",
- jobParameters.getBinarySecurityContextIdentifierValue());
- }
- }
-
- DaoMethodOutcome outcome = binaryDao.update(binary, srd);
- IIdType id = outcome.getId();
-
- BulkExportBinaryFileId bulkExportBinaryFileId = new BulkExportBinaryFileId();
- bulkExportBinaryFileId.setBinaryId(id.getValueAsString());
- bulkExportBinaryFileId.setResourceType(expandedResources.getResourceType());
- theDataSink.accept(bulkExportBinaryFileId);
-
- ourLog.info(
- "Binary writing complete for {} resources of type {}.",
- processedRecordsCount,
- expandedResources.getResourceType());
- }
- return new RunOutcome(numResourcesProcessed);
+ return new RunOutcome(resourceWriter.getNumResourcesProcessed());
}
- private List expandResourcesFromList(
- StepExecutionDetails theStepExecutionDetails) {
- List expandedResourcesList = new ArrayList<>();
- String instanceId = theStepExecutionDetails.getInstance().getInstanceId();
- String chunkId = theStepExecutionDetails.getChunkId();
+ private void expandResourcesFromList(
+ StepExecutionDetails theStepExecutionDetails,
+ Consumer theResourceWriter) {
+
ResourceIdList idList = theStepExecutionDetails.getData();
BulkExportJobParameters parameters = theStepExecutionDetails.getParameters();
- ourLog.info(
- "Bulk export instance[{}] chunk[{}] - About to expand {} resource IDs into their full resource bodies.",
- instanceId,
- chunkId,
- idList.getIds().size());
+ Consumer> resourceListConsumer =
+ new ExpandResourcesConsumer(theStepExecutionDetails, theResourceWriter);
// search the resources
- List allResources = fetchAllResources(idList, parameters.getPartitionId());
-
- // Apply post-fetch filtering
- String resourceType = idList.getResourceType();
- List postFetchFilterUrls = parameters.getPostFetchFilterUrls().stream()
- .filter(t -> t.substring(0, t.indexOf('?')).equals(resourceType))
- .collect(Collectors.toList());
-
- if (!postFetchFilterUrls.isEmpty()) {
- applyPostFetchFiltering(allResources, postFetchFilterUrls, instanceId, chunkId);
- }
-
- // if necessary, expand resources
- if (parameters.isExpandMdm()) {
- myBulkExportProcessor.expandMdmResources(allResources);
- }
-
- // Normalize terminology
- if (myStorageSettings.isNormalizeTerminologyForBulkExportJobs()) {
- ResponseTerminologyTranslationSvc terminologyTranslationSvc = myResponseTerminologyTranslationSvc;
- if (terminologyTranslationSvc == null) {
- terminologyTranslationSvc = myApplicationContext.getBean(ResponseTerminologyTranslationSvc.class);
- myResponseTerminologyTranslationSvc = terminologyTranslationSvc;
- }
- terminologyTranslationSvc.processResourcesForTerminologyTranslation(allResources);
- }
-
- // Interceptor call
- if (myInterceptorService.hasHooks(Pointcut.STORAGE_BULK_EXPORT_RESOURCE_INCLUSION)) {
- for (Iterator iter = allResources.iterator(); iter.hasNext(); ) {
- HookParams params = new HookParams()
- .add(BulkExportJobParameters.class, theStepExecutionDetails.getParameters())
- .add(IBaseResource.class, iter.next());
- boolean outcome =
- myInterceptorService.callHooks(Pointcut.STORAGE_BULK_EXPORT_RESOURCE_INCLUSION, params);
- if (!outcome) {
- iter.remove();
- }
- }
- }
-
- // encode them - Key is resource type, Value is a collection of serialized resources of that type
- ListMultimap resources = encodeToString(allResources, parameters);
-
- for (String nextResourceType : resources.keySet()) {
-
- ExpandedResourcesList output = new ExpandedResourcesList();
- output.setStringifiedResources(resources.get(nextResourceType));
- output.setResourceType(nextResourceType);
- expandedResourcesList.add(output);
-
- ourLog.info(
- "Expanding of {} resources of type {} completed",
- idList.getIds().size(),
- idList.getResourceType());
- }
- return expandedResourcesList;
+ fetchResourcesByIdAndConsumeThem(idList, parameters.getPartitionId(), resourceListConsumer);
}
- private void applyPostFetchFiltering(
- List theResources,
- List thePostFetchFilterUrls,
- String theInstanceId,
- String theChunkId) {
- int numRemoved = 0;
- for (Iterator iter = theResources.iterator(); iter.hasNext(); ) {
- boolean matched = applyPostFetchFilteringForSingleResource(thePostFetchFilterUrls, iter);
-
- if (!matched) {
- iter.remove();
- numRemoved++;
- }
- }
-
- if (numRemoved > 0) {
- ourLog.info(
- "Bulk export instance[{}] chunk[{}] - {} resources were filtered out because of post-fetch filter URLs",
- theInstanceId,
- theChunkId,
- numRemoved);
- }
- }
-
- private boolean applyPostFetchFilteringForSingleResource(
- List thePostFetchFilterUrls, Iterator iter) {
- IBaseResource nextResource = iter.next();
- String nextResourceType = myFhirContext.getResourceType(nextResource);
-
- for (String nextPostFetchFilterUrl : thePostFetchFilterUrls) {
- if (nextPostFetchFilterUrl.contains("?")) {
- String resourceType = nextPostFetchFilterUrl.substring(0, nextPostFetchFilterUrl.indexOf('?'));
- if (nextResourceType.equals(resourceType)) {
- InMemoryMatchResult matchResult = myInMemoryResourceMatcher.match(
- nextPostFetchFilterUrl, nextResource, null, new SystemRequestDetails());
- if (matchResult.matched()) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- private List fetchAllResources(ResourceIdList theIds, RequestPartitionId theRequestPartitionId) {
+ private void fetchResourcesByIdAndConsumeThem(
+ ResourceIdList theIds,
+ RequestPartitionId theRequestPartitionId,
+ Consumer> theResourceListConsumer) {
ArrayListMultimap typeToIds = ArrayListMultimap.create();
theIds.getIds().forEach(t -> typeToIds.put(t.getResourceType(), t.getId()));
- List resources = new ArrayList<>(theIds.getIds().size());
-
for (String resourceType : typeToIds.keySet()) {
IFhirResourceDao> dao = myDaoRegistry.getResourceDao(resourceType);
@@ -383,31 +203,9 @@ public class ExpandResourceAndWriteBinaryStep
SearchParameterMap spMap = SearchParameterMap.newSynchronous().add(PARAM_ID, idListParam);
IBundleProvider outcome =
dao.search(spMap, new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId));
- resources.addAll(outcome.getAllResources());
+ theResourceListConsumer.accept(outcome.getAllResources());
}
}
-
- return resources;
- }
-
- private ListMultimap encodeToString(
- List theResources, BulkExportJobParameters theParameters) {
- IParser parser = getParser(theParameters);
-
- ListMultimap retVal = ArrayListMultimap.create();
- for (IBaseResource resource : theResources) {
- String type = myFhirContext.getResourceType(resource);
- String jsonResource = parser.encodeResourceToString(resource);
- retVal.put(type, jsonResource);
- }
- return retVal;
- }
-
- private IParser getParser(BulkExportJobParameters theParameters) {
- // The parser depends on the output format
- // but for now, only ndjson is supported
- // see WriteBinaryStep as well
- return myFhirContext.newJsonParser().setPrettyPrint(false);
}
/**
@@ -462,4 +260,310 @@ public class ExpandResourceAndWriteBinaryStep
public void setIdHelperServiceForUnitTest(IIdHelperService theIdHelperService) {
myIdHelperService = theIdHelperService;
}
+
+ /**
+ * This class takes a collection of lists of resources read from the
+ * repository, and processes them, then converts them into
+ * {@link ExpandedResourcesList} instances, each one of which corresponds
+ * to a single output file. We try to avoid exceeding the maximum file
+ * size defined in
+ * {@link JpaStorageSettings#getBulkExportFileMaximumSize()}
+ * so we will do our best to emit multiple lists in favour of emitting
+ * a list that exceeds that threshold.
+ */
+ private class ExpandResourcesConsumer implements Consumer> {
+
+ private final Consumer myResourceWriter;
+ private final StepExecutionDetails myStepExecutionDetails;
+
+ public ExpandResourcesConsumer(
+ StepExecutionDetails theStepExecutionDetails,
+ Consumer theResourceWriter) {
+ myStepExecutionDetails = theStepExecutionDetails;
+ myResourceWriter = theResourceWriter;
+ }
+
+ @Override
+ public void accept(List theResources) throws JobExecutionFailedException {
+ String instanceId = myStepExecutionDetails.getInstance().getInstanceId();
+ String chunkId = myStepExecutionDetails.getChunkId();
+ ResourceIdList idList = myStepExecutionDetails.getData();
+ BulkExportJobParameters parameters = myStepExecutionDetails.getParameters();
+
+ ourLog.info(
+ "Bulk export instance[{}] chunk[{}] - About to expand {} resource IDs into their full resource bodies.",
+ instanceId,
+ chunkId,
+ idList.getIds().size());
+
+ // Apply post-fetch filtering
+ String resourceType = idList.getResourceType();
+ List postFetchFilterUrls = parameters.getPostFetchFilterUrls().stream()
+ .filter(t -> t.substring(0, t.indexOf('?')).equals(resourceType))
+ .collect(Collectors.toList());
+
+ if (!postFetchFilterUrls.isEmpty()) {
+ applyPostFetchFiltering(theResources, postFetchFilterUrls, instanceId, chunkId);
+ }
+
+ // if necessary, expand resources
+ if (parameters.isExpandMdm()) {
+ myBulkExportProcessor.expandMdmResources(theResources);
+ }
+
+ // Normalize terminology
+ if (myStorageSettings.isNormalizeTerminologyForBulkExportJobs()) {
+ ResponseTerminologyTranslationSvc terminologyTranslationSvc = myResponseTerminologyTranslationSvc;
+ if (terminologyTranslationSvc == null) {
+ terminologyTranslationSvc = myApplicationContext.getBean(ResponseTerminologyTranslationSvc.class);
+ myResponseTerminologyTranslationSvc = terminologyTranslationSvc;
+ }
+ terminologyTranslationSvc.processResourcesForTerminologyTranslation(theResources);
+ }
+
+ // Interceptor call
+ if (myInterceptorService.hasHooks(Pointcut.STORAGE_BULK_EXPORT_RESOURCE_INCLUSION)) {
+ for (Iterator iter = theResources.iterator(); iter.hasNext(); ) {
+ HookParams params = new HookParams()
+ .add(BulkExportJobParameters.class, myStepExecutionDetails.getParameters())
+ .add(IBaseResource.class, iter.next());
+ boolean outcome =
+ myInterceptorService.callHooks(Pointcut.STORAGE_BULK_EXPORT_RESOURCE_INCLUSION, params);
+ if (!outcome) {
+ iter.remove();
+ }
+ }
+ }
+
+ // encode them - Key is resource type, Value is a collection of serialized resources of that type
+ IParser parser = getParser(parameters);
+
+ ListMultimap resourceTypeToStringifiedResources = ArrayListMultimap.create();
+ Map resourceTypeToTotalSize = new HashMap<>();
+ for (IBaseResource resource : theResources) {
+ String type = myFhirContext.getResourceType(resource);
+ int existingSize = resourceTypeToTotalSize.getOrDefault(type, 0);
+
+ String jsonResource = parser.encodeResourceToString(resource);
+ int newSize = existingSize + jsonResource.length();
+
+ // If adding another stringified resource to the list for the given type
+ // would exceed the configured maximum allowed, then let's send the current
+ // list and flush it. Note that if a single resource exceeds the configurable
+ // maximum then we have no choice but to send it
+ long bulkExportFileMaximumSize = myStorageSettings.getBulkExportFileMaximumSize();
+ if (newSize > bulkExportFileMaximumSize) {
+ if (existingSize == 0) {
+ // If no files are already in the collection, then this one file
+ // is bigger than the maximum allowable. We'll allow it in that
+ // case
+ ourLog.warn(
+ "Single resource size {} exceeds allowable maximum of {}, so will ignore maximum",
+ newSize,
+ bulkExportFileMaximumSize);
+ } else {
+ // Otherwise, flush the contents now before adding the next file
+ List stringifiedResources = resourceTypeToStringifiedResources.get(type);
+ writeStringifiedResources(type, stringifiedResources);
+
+ resourceTypeToStringifiedResources.removeAll(type);
+ newSize = jsonResource.length();
+ }
+ }
+
+ resourceTypeToStringifiedResources.put(type, jsonResource);
+ resourceTypeToTotalSize.put(type, newSize);
+ }
+
+ for (String nextResourceType : resourceTypeToStringifiedResources.keySet()) {
+ List stringifiedResources = resourceTypeToStringifiedResources.get(nextResourceType);
+ writeStringifiedResources(nextResourceType, stringifiedResources);
+ }
+ }
+
+ private void writeStringifiedResources(String theResourceType, List theStringifiedResources) {
+ if (!theStringifiedResources.isEmpty()) {
+
+ ExpandedResourcesList output = new ExpandedResourcesList();
+ output.setStringifiedResources(theStringifiedResources);
+ output.setResourceType(theResourceType);
+ myResourceWriter.accept(output);
+
+ ourLog.info(
+ "Expanding of {} resources of type {} completed",
+ theStringifiedResources.size(),
+ theResourceType);
+ }
+ }
+
+ private void applyPostFetchFiltering(
+ List theResources,
+ List thePostFetchFilterUrls,
+ String theInstanceId,
+ String theChunkId) {
+ int numRemoved = 0;
+ for (Iterator iter = theResources.iterator(); iter.hasNext(); ) {
+ boolean matched = applyPostFetchFilteringForSingleResource(thePostFetchFilterUrls, iter);
+
+ if (!matched) {
+ iter.remove();
+ numRemoved++;
+ }
+ }
+
+ if (numRemoved > 0) {
+ ourLog.info(
+ "Bulk export instance[{}] chunk[{}] - {} resources were filtered out because of post-fetch filter URLs",
+ theInstanceId,
+ theChunkId,
+ numRemoved);
+ }
+ }
+
+ private boolean applyPostFetchFilteringForSingleResource(
+ List thePostFetchFilterUrls, Iterator iter) {
+ IBaseResource nextResource = iter.next();
+ String nextResourceType = myFhirContext.getResourceType(nextResource);
+
+ for (String nextPostFetchFilterUrl : thePostFetchFilterUrls) {
+ if (nextPostFetchFilterUrl.contains("?")) {
+ String resourceType = nextPostFetchFilterUrl.substring(0, nextPostFetchFilterUrl.indexOf('?'));
+ if (nextResourceType.equals(resourceType)) {
+ InMemoryMatchResult matchResult = myInMemoryResourceMatcher.match(
+ nextPostFetchFilterUrl, nextResource, null, new SystemRequestDetails());
+ if (matchResult.matched()) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private IParser getParser(BulkExportJobParameters theParameters) {
+ // The parser depends on the output format
+ // but for now, only ndjson is supported
+ // see WriteBinaryStep as well
+ return myFhirContext.newJsonParser().setPrettyPrint(false);
+ }
+ }
+
+ /**
+ * This class takes a collection of expanded resources, and expands it to
+ * an NDJSON file, which is written to a Binary resource.
+ */
+ private class NdJsonResourceWriter implements Consumer {
+
+ private final StepExecutionDetails myStepExecutionDetails;
+ private final IJobDataSink myDataSink;
+ private int myNumResourcesProcessed = 0;
+
+ public NdJsonResourceWriter(
+ StepExecutionDetails theStepExecutionDetails,
+ IJobDataSink theDataSink) {
+ this.myStepExecutionDetails = theStepExecutionDetails;
+ this.myDataSink = theDataSink;
+ }
+
+ public int getNumResourcesProcessed() {
+ return myNumResourcesProcessed;
+ }
+
+ @Override
+ public void accept(ExpandedResourcesList theExpandedResourcesList) throws JobExecutionFailedException {
+ int batchSize = theExpandedResourcesList.getStringifiedResources().size();
+ ourLog.info("Writing {} resources to binary file", batchSize);
+
+ myNumResourcesProcessed += batchSize;
+
+ @SuppressWarnings("unchecked")
+ IFhirResourceDao binaryDao = myDaoRegistry.getResourceDao("Binary");
+
+ IBaseBinary binary = BinaryUtil.newBinary(myFhirContext);
+
+ addMetadataExtensionsToBinary(myStepExecutionDetails, theExpandedResourcesList, binary);
+
+ binary.setContentType(Constants.CT_FHIR_NDJSON);
+
+ int processedRecordsCount = 0;
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ try (OutputStreamWriter streamWriter = getStreamWriter(outputStream)) {
+ for (String stringified : theExpandedResourcesList.getStringifiedResources()) {
+ streamWriter.append(stringified);
+ streamWriter.append("\n");
+ processedRecordsCount++;
+ }
+ streamWriter.flush();
+ outputStream.flush();
+ }
+ binary.setContent(outputStream.toByteArray());
+ } catch (IOException ex) {
+ String errorMsg = String.format(
+ "Failure to process resource of type %s : %s",
+ theExpandedResourcesList.getResourceType(), ex.getMessage());
+ ourLog.error(errorMsg);
+
+ throw new JobExecutionFailedException(Msg.code(2431) + errorMsg);
+ }
+
+ SystemRequestDetails srd = new SystemRequestDetails();
+ BulkExportJobParameters jobParameters = myStepExecutionDetails.getParameters();
+ RequestPartitionId partitionId = jobParameters.getPartitionId();
+ if (partitionId == null) {
+ srd.setRequestPartitionId(RequestPartitionId.defaultPartition());
+ } else {
+ srd.setRequestPartitionId(partitionId);
+ }
+
+ // Pick a unique ID and retry until we get one that isn't already used. This is just to
+ // avoid any possibility of people guessing the IDs of these Binaries and fishing for them.
+ while (true) {
+ // Use a random ID to make it harder to guess IDs - 32 characters of a-zA-Z0-9
+ // has 190 bts of entropy according to https://www.omnicalculator.com/other/password-entropy
+ String proposedId = RandomTextUtils.newSecureRandomAlphaNumericString(32);
+ binary.setId(proposedId);
+
+ // Make sure we don't accidentally reuse an ID. This should be impossible given the
+ // amount of entropy in the IDs but might as well be sure.
+ try {
+ IBaseBinary output = binaryDao.read(binary.getIdElement(), new SystemRequestDetails(), true);
+ if (output != null) {
+ continue;
+ }
+ } catch (ResourceNotFoundException e) {
+ // good
+ }
+
+ break;
+ }
+
+ if (myFhirContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) {
+ if (isNotBlank(jobParameters.getBinarySecurityContextIdentifierSystem())
+ || isNotBlank(jobParameters.getBinarySecurityContextIdentifierValue())) {
+ FhirTerser terser = myFhirContext.newTerser();
+ terser.setElement(
+ binary,
+ "securityContext.identifier.system",
+ jobParameters.getBinarySecurityContextIdentifierSystem());
+ terser.setElement(
+ binary,
+ "securityContext.identifier.value",
+ jobParameters.getBinarySecurityContextIdentifierValue());
+ }
+ }
+
+ DaoMethodOutcome outcome = binaryDao.update(binary, srd);
+ IIdType id = outcome.getId();
+
+ BulkExportBinaryFileId bulkExportBinaryFileId = new BulkExportBinaryFileId();
+ bulkExportBinaryFileId.setBinaryId(id.getValueAsString());
+ bulkExportBinaryFileId.setResourceType(theExpandedResourcesList.getResourceType());
+ myDataSink.accept(bulkExportBinaryFileId);
+
+ ourLog.info(
+ "Binary writing complete for {} resources of type {}.",
+ processedRecordsCount,
+ theExpandedResourcesList.getResourceType());
+ }
+ }
}
diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStep.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStep.java
index e2cce9c5f31..8c8f0490d9e 100644
--- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStep.java
+++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStep.java
@@ -26,18 +26,19 @@ import ca.uhn.fhir.batch2.api.RunOutcome;
import ca.uhn.fhir.batch2.api.StepExecutionDetails;
import ca.uhn.fhir.batch2.jobs.export.models.ExpandedResourcesList;
import ca.uhn.fhir.batch2.jobs.export.models.ResourceIdList;
+import ca.uhn.fhir.batch2.jobs.models.BatchResourceId;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
+import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.PersistentIdToForcedIdMap;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.bulk.export.api.IBulkExportProcessor;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
-import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher;
@@ -52,6 +53,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import jakarta.annotation.Nonnull;
+import org.apache.commons.collections4.ListUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
@@ -84,7 +86,7 @@ public class ExpandResourcesStep
private ApplicationContext myApplicationContext;
@Autowired
- private StorageSettings myStorageSettings;
+ private JpaStorageSettings myStorageSettings;
@Autowired
private IIdHelperService myIdHelperService;
@@ -108,72 +110,99 @@ public class ExpandResourcesStep
throws JobExecutionFailedException {
String instanceId = theStepExecutionDetails.getInstance().getInstanceId();
String chunkId = theStepExecutionDetails.getChunkId();
- ResourceIdList idList = theStepExecutionDetails.getData();
+ ResourceIdList data = theStepExecutionDetails.getData();
BulkExportJobParameters parameters = theStepExecutionDetails.getParameters();
ourLog.info(
"Bulk export instance[{}] chunk[{}] - About to expand {} resource IDs into their full resource bodies.",
instanceId,
chunkId,
- idList.getIds().size());
+ data.getIds().size());
- // search the resources
- List allResources = fetchAllResources(idList, parameters.getPartitionId());
+ // Partition the ID list in order to only fetch a reasonable number at a time
+ List> idLists = ListUtils.partition(data.getIds(), 100);
- // Apply post-fetch filtering
- String resourceType = idList.getResourceType();
- List postFetchFilterUrls = parameters.getPostFetchFilterUrls().stream()
- .filter(t -> t.substring(0, t.indexOf('?')).equals(resourceType))
- .collect(Collectors.toList());
+ for (List idList : idLists) {
- if (!postFetchFilterUrls.isEmpty()) {
- applyPostFetchFiltering(allResources, postFetchFilterUrls, instanceId, chunkId);
- }
+ // search the resources
+ List allResources = fetchAllResources(idList, parameters.getPartitionId());
- // if necessary, expand resources
- if (parameters.isExpandMdm()) {
- myBulkExportProcessor.expandMdmResources(allResources);
- }
+ // Apply post-fetch filtering
+ String resourceType = data.getResourceType();
+ List postFetchFilterUrls = parameters.getPostFetchFilterUrls().stream()
+ .filter(t -> t.substring(0, t.indexOf('?')).equals(resourceType))
+ .collect(Collectors.toList());
- // Normalize terminology
- if (myStorageSettings.isNormalizeTerminologyForBulkExportJobs()) {
- ResponseTerminologyTranslationSvc terminologyTranslationSvc = myResponseTerminologyTranslationSvc;
- if (terminologyTranslationSvc == null) {
- terminologyTranslationSvc = myApplicationContext.getBean(ResponseTerminologyTranslationSvc.class);
- myResponseTerminologyTranslationSvc = terminologyTranslationSvc;
+ if (!postFetchFilterUrls.isEmpty()) {
+ applyPostFetchFiltering(allResources, postFetchFilterUrls, instanceId, chunkId);
}
- terminologyTranslationSvc.processResourcesForTerminologyTranslation(allResources);
- }
- // Interceptor call
- if (myInterceptorService.hasHooks(Pointcut.STORAGE_BULK_EXPORT_RESOURCE_INCLUSION)) {
- for (Iterator iter = allResources.iterator(); iter.hasNext(); ) {
- HookParams params = new HookParams()
- .add(BulkExportJobParameters.class, theStepExecutionDetails.getParameters())
- .add(IBaseResource.class, iter.next());
- boolean outcome =
- myInterceptorService.callHooks(Pointcut.STORAGE_BULK_EXPORT_RESOURCE_INCLUSION, params);
- if (!outcome) {
- iter.remove();
+ // if necessary, expand resources
+ if (parameters.isExpandMdm()) {
+ myBulkExportProcessor.expandMdmResources(allResources);
+ }
+
+ // Normalize terminology
+ if (myStorageSettings.isNormalizeTerminologyForBulkExportJobs()) {
+ ResponseTerminologyTranslationSvc terminologyTranslationSvc = myResponseTerminologyTranslationSvc;
+ if (terminologyTranslationSvc == null) {
+ terminologyTranslationSvc = myApplicationContext.getBean(ResponseTerminologyTranslationSvc.class);
+ myResponseTerminologyTranslationSvc = terminologyTranslationSvc;
+ }
+ terminologyTranslationSvc.processResourcesForTerminologyTranslation(allResources);
+ }
+
+ // Interceptor call
+ if (myInterceptorService.hasHooks(Pointcut.STORAGE_BULK_EXPORT_RESOURCE_INCLUSION)) {
+ for (Iterator iter = allResources.iterator(); iter.hasNext(); ) {
+ HookParams params = new HookParams()
+ .add(BulkExportJobParameters.class, theStepExecutionDetails.getParameters())
+ .add(IBaseResource.class, iter.next());
+ boolean outcome =
+ myInterceptorService.callHooks(Pointcut.STORAGE_BULK_EXPORT_RESOURCE_INCLUSION, params);
+ if (!outcome) {
+ iter.remove();
+ }
}
}
- }
- // encode them - Key is resource type, Value is a collection of serialized resources of that type
- ListMultimap resources = encodeToString(allResources, parameters);
+ // encode them - Key is resource type, Value is a collection of serialized resources of that type
+ ListMultimap resources = encodeToString(allResources, parameters);
- // set to datasink
- for (String nextResourceType : resources.keySet()) {
+ // send to datasink
+ long maxFileSize = myStorageSettings.getBulkExportFileMaximumSize();
+ long currentFileSize = 0;
+ for (String nextResourceType : resources.keySet()) {
- ExpandedResourcesList output = new ExpandedResourcesList();
- output.setStringifiedResources(resources.get(nextResourceType));
- output.setResourceType(nextResourceType);
- theDataSink.accept(output);
+ List stringifiedResources = resources.get(nextResourceType);
+ List currentFileStringifiedResources = new ArrayList<>();
- ourLog.info(
- "Expanding of {} resources of type {} completed",
- idList.getIds().size(),
- idList.getResourceType());
+ for (String nextStringifiedResource : stringifiedResources) {
+
+ if (currentFileSize + nextStringifiedResource.length() > maxFileSize
+ && !currentFileStringifiedResources.isEmpty()) {
+ ExpandedResourcesList output = new ExpandedResourcesList();
+ output.setStringifiedResources(currentFileStringifiedResources);
+ output.setResourceType(nextResourceType);
+ theDataSink.accept(output);
+
+ currentFileStringifiedResources = new ArrayList<>();
+ currentFileSize = 0;
+ }
+
+ currentFileStringifiedResources.add(nextStringifiedResource);
+ currentFileSize += nextStringifiedResource.length();
+ }
+
+ if (!currentFileStringifiedResources.isEmpty()) {
+ ExpandedResourcesList output = new ExpandedResourcesList();
+ output.setStringifiedResources(currentFileStringifiedResources);
+ output.setResourceType(nextResourceType);
+ theDataSink.accept(output);
+ }
+
+ ourLog.info("Expanding of {} resources of type {} completed", idList.size(), data.getResourceType());
+ }
}
// and return
@@ -224,42 +253,36 @@ public class ExpandResourcesStep
return false;
}
- private List fetchAllResources(ResourceIdList theIds, RequestPartitionId theRequestPartitionId) {
+ private List fetchAllResources(
+ List theIds, RequestPartitionId theRequestPartitionId) {
ArrayListMultimap typeToIds = ArrayListMultimap.create();
- theIds.getIds().forEach(t -> typeToIds.put(t.getResourceType(), t.getId()));
+ theIds.forEach(t -> typeToIds.put(t.getResourceType(), t.getId()));
- List resources = new ArrayList<>(theIds.getIds().size());
+ List resources = new ArrayList<>(theIds.size());
for (String resourceType : typeToIds.keySet()) {
IFhirResourceDao> dao = myDaoRegistry.getResourceDao(resourceType);
List allIds = typeToIds.get(resourceType);
- while (!allIds.isEmpty()) {
- // Load in batches in order to avoid having too many PIDs go into a
- // single SQ statement at once
- int batchSize = Math.min(500, allIds.size());
+ Set nextBatchOfPids = allIds.stream()
+ .map(t -> myIdHelperService.newPidFromStringIdAndResourceName(t, resourceType))
+ .collect(Collectors.toSet());
- Set nextBatchOfPids = allIds.subList(0, batchSize).stream()
- .map(t -> myIdHelperService.newPidFromStringIdAndResourceName(t, resourceType))
- .collect(Collectors.toSet());
- allIds = allIds.subList(batchSize, allIds.size());
+ PersistentIdToForcedIdMap nextBatchOfResourceIds = myTransactionService
+ .withRequest(null)
+ .execute(() -> myIdHelperService.translatePidsToForcedIds(nextBatchOfPids));
- PersistentIdToForcedIdMap nextBatchOfResourceIds = myTransactionService
- .withRequest(null)
- .execute(() -> myIdHelperService.translatePidsToForcedIds(nextBatchOfPids));
-
- TokenOrListParam idListParam = new TokenOrListParam();
- for (IResourcePersistentId nextPid : nextBatchOfPids) {
- Optional resourceId = nextBatchOfResourceIds.get(nextPid);
- idListParam.add(resourceId.orElse(nextPid.getId().toString()));
- }
-
- SearchParameterMap spMap = SearchParameterMap.newSynchronous().add(PARAM_ID, idListParam);
- IBundleProvider outcome =
- dao.search(spMap, new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId));
- resources.addAll(outcome.getAllResources());
+ TokenOrListParam idListParam = new TokenOrListParam();
+ for (IResourcePersistentId nextPid : nextBatchOfPids) {
+ Optional resourceId = nextBatchOfResourceIds.get(nextPid);
+ idListParam.add(resourceId.orElse(nextPid.getId().toString()));
}
+
+ SearchParameterMap spMap = SearchParameterMap.newSynchronous().add(PARAM_ID, idListParam);
+ IBundleProvider outcome =
+ dao.search(spMap, new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId));
+ resources.addAll(outcome.getAllResources());
}
return resources;
diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/FetchResourceIdsStep.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/FetchResourceIdsStep.java
index 16ec8987a1d..e576f9362b4 100644
--- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/FetchResourceIdsStep.java
+++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/FetchResourceIdsStep.java
@@ -102,6 +102,8 @@ public class FetchResourceIdsStep implements IFirstJobStepWorker idsToSubmit = new ArrayList<>();
+ int estimatedChunkSize = 0;
+
if (!pidIterator.hasNext()) {
ourLog.debug("Bulk Export generated an iterator with no results!");
}
@@ -121,17 +123,25 @@ public class FetchResourceIdsStep implements IFirstJobStepWorker 0) {
+ // Account for comma between array entries
+ estimatedChunkSize++;
+ }
+ estimatedChunkSize += batchResourceId.estimateSerializedSize();
+
// Make sure resources stored in each batch does not go over the max capacity
- if (idsToSubmit.size() >= myStorageSettings.getBulkExportFileMaximumCapacity()) {
- submitWorkChunk(idsToSubmit, resourceType, params, theDataSink);
+ if (idsToSubmit.size() >= myStorageSettings.getBulkExportFileMaximumCapacity()
+ || estimatedChunkSize >= myStorageSettings.getBulkExportFileMaximumSize()) {
+ submitWorkChunk(idsToSubmit, resourceType, theDataSink);
submissionCount++;
idsToSubmit = new ArrayList<>();
+ estimatedChunkSize = 0;
}
}
// if we have any other Ids left, submit them now
if (!idsToSubmit.isEmpty()) {
- submitWorkChunk(idsToSubmit, resourceType, params, theDataSink);
+ submitWorkChunk(idsToSubmit, resourceType, theDataSink);
submissionCount++;
}
}
@@ -150,7 +160,6 @@ public class FetchResourceIdsStep implements IFirstJobStepWorker theBatchResourceIds,
String theResourceType,
- BulkExportJobParameters theParams,
IJobDataSink theDataSink) {
ResourceIdList idList = new ResourceIdList();
diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/models/BatchResourceId.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/models/BatchResourceId.java
index 61c3a5daf64..4070d4d1a20 100644
--- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/models/BatchResourceId.java
+++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/models/BatchResourceId.java
@@ -22,10 +22,13 @@ package ca.uhn.fhir.batch2.jobs.models;
import ca.uhn.fhir.model.api.IModelJson;
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
-public class BatchResourceId implements IModelJson {
+import static org.apache.commons.lang3.StringUtils.defaultString;
+
+public class BatchResourceId implements IModelJson, Comparable {
@JsonProperty("type")
private String myResourceType;
@@ -77,6 +80,24 @@ public class BatchResourceId implements IModelJson {
return new HashCodeBuilder(17, 37).append(myResourceType).append(myId).toHashCode();
}
+ /**
+ * Returns an estimate of how long the JSON serialized (non-pretty printed) form
+ * of this object will be.
+ */
+ public int estimateSerializedSize() {
+ // 19 chars: {"id":"","type":""}
+ return 19 + defaultString(myId).length() + defaultString(myResourceType).length();
+ }
+
+ @Override
+ public int compareTo(@Nonnull BatchResourceId o) {
+ int retVal = o.myResourceType.compareTo(myResourceType);
+ if (retVal == 0) {
+ retVal = o.myId.compareTo(myId);
+ }
+ return retVal;
+ }
+
public static BatchResourceId getIdFromPID(IResourcePersistentId thePID, String theResourceType) {
BatchResourceId batchResourceId = new BatchResourceId();
batchResourceId.setId(thePID.getId().toString());
diff --git a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourceAndWriteBinaryStepTest.java b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourceAndWriteBinaryStepTest.java
index 576da3269aa..48b63d094ec 100644
--- a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourceAndWriteBinaryStepTest.java
+++ b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourceAndWriteBinaryStepTest.java
@@ -12,6 +12,7 @@ import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
+import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
@@ -112,7 +113,7 @@ public class ExpandResourceAndWriteBinaryStepTest {
private FhirContext myFhirContext = FhirContext.forR4Cached();
@Spy
- private StorageSettings myStorageSettings = new StorageSettings();
+ private JpaStorageSettings myStorageSettings = new JpaStorageSettings();
@Spy
private IHapiTransactionService myTransactionService = new NonTransactionalHapiTransactionService();
diff --git a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStepTest.java b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStepTest.java
index 5ec874649fe..b1ce4c1d63a 100644
--- a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStepTest.java
+++ b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStepTest.java
@@ -4,14 +4,14 @@ package ca.uhn.fhir.batch2.jobs.export;
import ca.uhn.fhir.batch2.api.IJobDataSink;
import ca.uhn.fhir.batch2.api.RunOutcome;
import ca.uhn.fhir.batch2.api.StepExecutionDetails;
-import ca.uhn.fhir.interceptor.executor.InterceptorService;
-import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
import ca.uhn.fhir.batch2.jobs.export.models.ExpandedResourcesList;
import ca.uhn.fhir.batch2.jobs.export.models.ResourceIdList;
import ca.uhn.fhir.batch2.jobs.models.BatchResourceId;
import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
+import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.PersistentIdToForcedIdMap;
@@ -20,8 +20,8 @@ import ca.uhn.fhir.jpa.bulk.export.api.IBulkExportProcessor;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
import ca.uhn.fhir.jpa.dao.tx.NonTransactionalHapiTransactionService;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
-import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
+import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.interceptor.ResponseTerminologyTranslationSvc;
@@ -75,7 +75,7 @@ public class ExpandResourcesStepTest {
private FhirContext myFhirContext = FhirContext.forR4Cached();
@Spy
- private StorageSettings myStorageSettings = new StorageSettings();
+ private JpaStorageSettings myStorageSettings = new JpaStorageSettings();
@Spy
private IHapiTransactionService myTransactionService = new NonTransactionalHapiTransactionService();
diff --git a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/FetchResourceIdsStepTest.java b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/FetchResourceIdsStepTest.java
index da80c5bea69..14e717d7ebd 100644
--- a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/FetchResourceIdsStepTest.java
+++ b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/export/FetchResourceIdsStepTest.java
@@ -130,6 +130,7 @@ public class FetchResourceIdsStepTest {
.thenReturn(observationIds.iterator());
int maxFileCapacity = 1000;
when(myStorageSettings.getBulkExportFileMaximumCapacity()).thenReturn(maxFileCapacity);
+ when(myStorageSettings.getBulkExportFileMaximumSize()).thenReturn(10000L);
// test
RunOutcome outcome = myFirstStep.run(input, sink);
@@ -191,6 +192,7 @@ public class FetchResourceIdsStepTest {
// when
int maxFileCapacity = 5;
when(myStorageSettings.getBulkExportFileMaximumCapacity()).thenReturn(maxFileCapacity);
+ when(myStorageSettings.getBulkExportFileMaximumSize()).thenReturn(10000L);
for (int i = 0; i <= maxFileCapacity; i++) {
JpaPid id = JpaPid.fromId((long) i);
diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml
index fc56dda9e44..ce8ac389e76 100644
--- a/hapi-fhir-storage-batch2-test-utilities/pom.xml
+++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml
index f48578a48ac..2d86204e222 100644
--- a/hapi-fhir-storage-batch2/pom.xml
+++ b/hapi-fhir-storage-batch2/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml
index 1c37b3375d2..deec953b4d9 100644
--- a/hapi-fhir-storage-cr/pom.xml
+++ b/hapi-fhir-storage-cr/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/CanonicalHelper.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/CanonicalHelper.java
new file mode 100644
index 00000000000..a0eee8f5175
--- /dev/null
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/CanonicalHelper.java
@@ -0,0 +1,50 @@
+/*-
+ * #%L
+ * HAPI FHIR - Clinical Reasoning
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.cr.common;
+
+import ca.uhn.fhir.context.FhirVersionEnum;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+
+public class CanonicalHelper {
+ public static > C getCanonicalType(
+ FhirVersionEnum fhirVersion, String theCanonical, String theUrl, String theVersion) {
+ String url = theVersion == null ? theUrl : String.format("%s|%s", theUrl, theVersion);
+ String canonical = theCanonical == null ? url : theCanonical;
+ return newCanonicalType(fhirVersion, canonical);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static > C newCanonicalType(
+ FhirVersionEnum fhirVersion, String theCanonical) {
+ if (theCanonical == null) {
+ return null;
+ }
+ switch (fhirVersion) {
+ case DSTU3:
+ return (C) new org.hl7.fhir.dstu3.model.StringType(theCanonical);
+ case R4:
+ return (C) new org.hl7.fhir.r4.model.CanonicalType(theCanonical);
+ case R5:
+ return (C) new org.hl7.fhir.r5.model.CanonicalType(theCanonical);
+ default:
+ return null;
+ }
+ }
+}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IActivityDefinitionProcessorFactory.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IActivityDefinitionProcessorFactory.java
similarity index 88%
rename from hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IActivityDefinitionProcessorFactory.java
rename to hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IActivityDefinitionProcessorFactory.java
index 996d1127b31..5cb284a9873 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IActivityDefinitionProcessorFactory.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IActivityDefinitionProcessorFactory.java
@@ -17,10 +17,10 @@
* limitations under the License.
* #L%
*/
-package ca.uhn.fhir.cr.r4;
+package ca.uhn.fhir.cr.common;
import ca.uhn.fhir.rest.api.server.RequestDetails;
-import org.opencds.cqf.fhir.cr.activitydefinition.r4.ActivityDefinitionProcessor;
+import org.opencds.cqf.fhir.cr.activitydefinition.ActivityDefinitionProcessor;
@FunctionalInterface
public interface IActivityDefinitionProcessorFactory {
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IPlanDefinitionProcessorFactory.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IPlanDefinitionProcessorFactory.java
similarity index 89%
rename from hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IPlanDefinitionProcessorFactory.java
rename to hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IPlanDefinitionProcessorFactory.java
index c370600573d..7822a59a6c7 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IPlanDefinitionProcessorFactory.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IPlanDefinitionProcessorFactory.java
@@ -17,10 +17,10 @@
* limitations under the License.
* #L%
*/
-package ca.uhn.fhir.cr.r4;
+package ca.uhn.fhir.cr.common;
import ca.uhn.fhir.rest.api.server.RequestDetails;
-import org.opencds.cqf.fhir.cr.plandefinition.r4.PlanDefinitionProcessor;
+import org.opencds.cqf.fhir.cr.plandefinition.PlanDefinitionProcessor;
@FunctionalInterface
public interface IPlanDefinitionProcessorFactory {
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IQuestionnaireProcessorFactory.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IQuestionnaireProcessorFactory.java
similarity index 88%
rename from hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IQuestionnaireProcessorFactory.java
rename to hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IQuestionnaireProcessorFactory.java
index d08f284fe8e..e9f1e153030 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IQuestionnaireProcessorFactory.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IQuestionnaireProcessorFactory.java
@@ -17,10 +17,10 @@
* limitations under the License.
* #L%
*/
-package ca.uhn.fhir.cr.r4;
+package ca.uhn.fhir.cr.common;
import ca.uhn.fhir.rest.api.server.RequestDetails;
-import org.opencds.cqf.fhir.cr.questionnaire.r4.processor.QuestionnaireProcessor;
+import org.opencds.cqf.fhir.cr.questionnaire.QuestionnaireProcessor;
@FunctionalInterface
public interface IQuestionnaireProcessorFactory {
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IQuestionnaireResponseProcessorFactory.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IQuestionnaireResponseProcessorFactory.java
similarity index 88%
rename from hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IQuestionnaireResponseProcessorFactory.java
rename to hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IQuestionnaireResponseProcessorFactory.java
index d3e3d3b1719..e6d9ebc87c1 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/IQuestionnaireResponseProcessorFactory.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/common/IQuestionnaireResponseProcessorFactory.java
@@ -17,10 +17,10 @@
* limitations under the License.
* #L%
*/
-package ca.uhn.fhir.cr.r4;
+package ca.uhn.fhir.cr.common;
import ca.uhn.fhir.rest.api.server.RequestDetails;
-import org.opencds.cqf.fhir.cr.questionnaireresponse.r4.QuestionnaireResponseProcessor;
+import org.opencds.cqf.fhir.cr.questionnaireresponse.QuestionnaireResponseProcessor;
@FunctionalInterface
public interface IQuestionnaireResponseProcessorFactory {
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/CrProcessorConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/CrProcessorConfig.java
similarity index 69%
rename from hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/CrProcessorConfig.java
rename to hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/CrProcessorConfig.java
index d0d7704958c..a83b10d0b2d 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/CrProcessorConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/CrProcessorConfig.java
@@ -17,7 +17,7 @@
* limitations under the License.
* #L%
*/
-package ca.uhn.fhir.cr.config.r4;
+package ca.uhn.fhir.cr.config;
import ca.uhn.fhir.cr.common.IRepositoryFactory;
import org.opencds.cqf.fhir.cql.EvaluationSettings;
@@ -27,30 +27,30 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class CrProcessorConfig {
@Bean
- ca.uhn.fhir.cr.r4.IActivityDefinitionProcessorFactory r4ActivityDefinitionProcessorFactory(
+ ca.uhn.fhir.cr.common.IActivityDefinitionProcessorFactory activityDefinitionProcessorFactory(
IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
- return rd -> new org.opencds.cqf.fhir.cr.activitydefinition.r4.ActivityDefinitionProcessor(
+ return rd -> new org.opencds.cqf.fhir.cr.activitydefinition.ActivityDefinitionProcessor(
theRepositoryFactory.create(rd), theEvaluationSettings);
}
@Bean
- ca.uhn.fhir.cr.r4.IPlanDefinitionProcessorFactory r4PlanDefinitionProcessorFactory(
+ ca.uhn.fhir.cr.common.IPlanDefinitionProcessorFactory planDefinitionProcessorFactory(
IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
- return rd -> new org.opencds.cqf.fhir.cr.plandefinition.r4.PlanDefinitionProcessor(
+ return rd -> new org.opencds.cqf.fhir.cr.plandefinition.PlanDefinitionProcessor(
theRepositoryFactory.create(rd), theEvaluationSettings);
}
@Bean
- ca.uhn.fhir.cr.r4.IQuestionnaireProcessorFactory r4QuestionnaireProcessorFactory(
+ ca.uhn.fhir.cr.common.IQuestionnaireProcessorFactory questionnaireProcessorFactory(
IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
- return rd -> new org.opencds.cqf.fhir.cr.questionnaire.r4.processor.QuestionnaireProcessor(
+ return rd -> new org.opencds.cqf.fhir.cr.questionnaire.QuestionnaireProcessor(
theRepositoryFactory.create(rd), theEvaluationSettings);
}
@Bean
- ca.uhn.fhir.cr.r4.IQuestionnaireResponseProcessorFactory r4QuestionnaireResponseProcessorFactory(
+ ca.uhn.fhir.cr.common.IQuestionnaireResponseProcessorFactory questionnaireResponseProcessorFactory(
IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
- return rd -> new org.opencds.cqf.fhir.cr.questionnaireresponse.r4.QuestionnaireResponseProcessor(
+ return rd -> new org.opencds.cqf.fhir.cr.questionnaireresponse.QuestionnaireResponseProcessor(
theRepositoryFactory.create(rd), theEvaluationSettings);
}
}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/ApplyOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/ApplyOperationConfig.java
index b8a9adb3d7d..71c16e74a65 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/ApplyOperationConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/ApplyOperationConfig.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.cr.config.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/CrProcessorConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/CrProcessorConfig.java
deleted file mode 100644
index 1ffd8985a89..00000000000
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/CrProcessorConfig.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*-
- * #%L
- * HAPI FHIR - Clinical Reasoning
- * %%
- * Copyright (C) 2014 - 2024 Smile CDR, Inc.
- * %%
- * 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%
- */
-package ca.uhn.fhir.cr.config.dstu3;
-
-import ca.uhn.fhir.cr.common.IRepositoryFactory;
-import org.opencds.cqf.fhir.cql.EvaluationSettings;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class CrProcessorConfig {
- @Bean
- ca.uhn.fhir.cr.dstu3.IActivityDefinitionProcessorFactory dstu3ActivityDefinitionProcessorFactory(
- IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
- return rd -> new org.opencds.cqf.fhir.cr.activitydefinition.dstu3.ActivityDefinitionProcessor(
- theRepositoryFactory.create(rd), theEvaluationSettings);
- }
-
- @Bean
- ca.uhn.fhir.cr.dstu3.IPlanDefinitionProcessorFactory dstu3PlanDefinitionProcessorFactory(
- IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
- return rd -> new org.opencds.cqf.fhir.cr.plandefinition.dstu3.PlanDefinitionProcessor(
- theRepositoryFactory.create(rd), theEvaluationSettings);
- }
-
- @Bean
- ca.uhn.fhir.cr.dstu3.IQuestionnaireProcessorFactory dstu3QuestionnaireProcessorFactory(
- IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
- return rd -> new org.opencds.cqf.fhir.cr.questionnaire.dstu3.processor.QuestionnaireProcessor(
- theRepositoryFactory.create(rd), theEvaluationSettings);
- }
-
- @Bean
- ca.uhn.fhir.cr.dstu3.IQuestionnaireResponseProcessorFactory dstu3QuestionnaireResponseProcessorFactory(
- IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
- return rd -> new org.opencds.cqf.fhir.cr.questionnaireresponse.dstu3.QuestionnaireResponseProcessor(
- theRepositoryFactory.create(rd), theEvaluationSettings);
- }
-}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/ExtractOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/ExtractOperationConfig.java
index e58a5305c2f..2ea4d039801 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/ExtractOperationConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/ExtractOperationConfig.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.cr.config.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/PackageOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/PackageOperationConfig.java
index 5fb0c3a0345..8be36b6777e 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/PackageOperationConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/PackageOperationConfig.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.cr.config.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/PopulateOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/PopulateOperationConfig.java
index a60f57d594c..eedd452b5ab 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/PopulateOperationConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/PopulateOperationConfig.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.cr.config.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/QuestionnaireOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/QuestionnaireOperationConfig.java
new file mode 100644
index 00000000000..0e25fe57e09
--- /dev/null
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/QuestionnaireOperationConfig.java
@@ -0,0 +1,53 @@
+/*-
+ * #%L
+ * HAPI FHIR - Clinical Reasoning
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.cr.config.dstu3;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.ProviderLoader;
+import ca.uhn.fhir.cr.config.ProviderSelector;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+
+import java.util.Arrays;
+import java.util.Map;
+
+public class QuestionnaireOperationConfig {
+ @Bean
+ ca.uhn.fhir.cr.dstu3.structuredefinition.StructureDefinitionQuestionnaireProvider
+ dstu3StructureDefinitionQuestionnaireProvider() {
+ return new ca.uhn.fhir.cr.dstu3.structuredefinition.StructureDefinitionQuestionnaireProvider();
+ }
+
+ @Bean(name = "questionnaireOperationLoader")
+ public ProviderLoader questionnaireOperationLoader(
+ ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
+ var selector = new ProviderSelector(
+ theFhirContext,
+ Map.of(
+ FhirVersionEnum.DSTU3,
+ Arrays.asList(
+ ca.uhn.fhir.cr.dstu3.structuredefinition.StructureDefinitionQuestionnaireProvider
+ .class)));
+
+ return new ProviderLoader(theRestfulServer, theApplicationContext, selector);
+ }
+}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/ApplyOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/ApplyOperationConfig.java
index d67f7dc3963..2c859695c0b 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/ApplyOperationConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/ApplyOperationConfig.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.cr.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/CrR4Config.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/CrR4Config.java
index 716055860e1..f08743d7909 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/CrR4Config.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/CrR4Config.java
@@ -101,7 +101,6 @@ public class CrR4Config {
theCareGapsProperties,
theRepositoryFactory.create(rd),
theMeasureEvaluationOptions,
- theExecutor,
rd.getFhirServerBase());
}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/ExtractOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/ExtractOperationConfig.java
index b2a4f04fd64..a2be7b791f5 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/ExtractOperationConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/ExtractOperationConfig.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.cr.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/PackageOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/PackageOperationConfig.java
index f02d092c417..9f492e095f9 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/PackageOperationConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/PackageOperationConfig.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.cr.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/PopulateOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/PopulateOperationConfig.java
index ea91d88a727..ad7fdcd85c1 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/PopulateOperationConfig.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/PopulateOperationConfig.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.cr.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/QuestionnaireOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/QuestionnaireOperationConfig.java
new file mode 100644
index 00000000000..12ee29eeb63
--- /dev/null
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/QuestionnaireOperationConfig.java
@@ -0,0 +1,52 @@
+/*-
+ * #%L
+ * HAPI FHIR - Clinical Reasoning
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.cr.config.r4;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.config.ProviderLoader;
+import ca.uhn.fhir.cr.config.ProviderSelector;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+
+import java.util.Arrays;
+import java.util.Map;
+
+public class QuestionnaireOperationConfig {
+ @Bean
+ ca.uhn.fhir.cr.r4.structuredefinition.StructureDefinitionQuestionnaireProvider
+ r4StructureDefinitionQuestionnaireProvider() {
+ return new ca.uhn.fhir.cr.r4.structuredefinition.StructureDefinitionQuestionnaireProvider();
+ }
+
+ @Bean(name = "questionnaireOperationLoader")
+ public ProviderLoader questionnaireOperationLoader(
+ ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
+ var selector = new ProviderSelector(
+ theFhirContext,
+ Map.of(
+ FhirVersionEnum.R4,
+ Arrays.asList(
+ ca.uhn.fhir.cr.r4.structuredefinition.StructureDefinitionQuestionnaireProvider.class)));
+
+ return new ProviderLoader(theRestfulServer, theApplicationContext, selector);
+ }
+}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IActivityDefinitionProcessorFactory.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IActivityDefinitionProcessorFactory.java
deleted file mode 100644
index e57856900c0..00000000000
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IActivityDefinitionProcessorFactory.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*-
- * #%L
- * HAPI FHIR - Clinical Reasoning
- * %%
- * Copyright (C) 2014 - 2024 Smile CDR, Inc.
- * %%
- * 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%
- */
-package ca.uhn.fhir.cr.dstu3;
-
-import ca.uhn.fhir.rest.api.server.RequestDetails;
-import org.opencds.cqf.fhir.cr.activitydefinition.dstu3.ActivityDefinitionProcessor;
-
-@FunctionalInterface
-public interface IActivityDefinitionProcessorFactory {
- ActivityDefinitionProcessor create(RequestDetails theRequestDetails);
-}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IPlanDefinitionProcessorFactory.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IPlanDefinitionProcessorFactory.java
deleted file mode 100644
index d4494e8d908..00000000000
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IPlanDefinitionProcessorFactory.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*-
- * #%L
- * HAPI FHIR - Clinical Reasoning
- * %%
- * Copyright (C) 2014 - 2024 Smile CDR, Inc.
- * %%
- * 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%
- */
-package ca.uhn.fhir.cr.dstu3;
-
-import ca.uhn.fhir.rest.api.server.RequestDetails;
-import org.opencds.cqf.fhir.cr.plandefinition.dstu3.PlanDefinitionProcessor;
-
-@FunctionalInterface
-public interface IPlanDefinitionProcessorFactory {
- PlanDefinitionProcessor create(RequestDetails theRequestDetails);
-}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IQuestionnaireProcessorFactory.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IQuestionnaireProcessorFactory.java
deleted file mode 100644
index 68ea8e1c023..00000000000
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IQuestionnaireProcessorFactory.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*-
- * #%L
- * HAPI FHIR - Clinical Reasoning
- * %%
- * Copyright (C) 2014 - 2024 Smile CDR, Inc.
- * %%
- * 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%
- */
-package ca.uhn.fhir.cr.dstu3;
-
-import ca.uhn.fhir.rest.api.server.RequestDetails;
-import org.opencds.cqf.fhir.cr.questionnaire.dstu3.processor.QuestionnaireProcessor;
-
-@FunctionalInterface
-public interface IQuestionnaireProcessorFactory {
- QuestionnaireProcessor create(RequestDetails theRequestDetails);
-}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IQuestionnaireResponseProcessorFactory.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IQuestionnaireResponseProcessorFactory.java
deleted file mode 100644
index e699630f7dd..00000000000
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/IQuestionnaireResponseProcessorFactory.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*-
- * #%L
- * HAPI FHIR - Clinical Reasoning
- * %%
- * Copyright (C) 2014 - 2024 Smile CDR, Inc.
- * %%
- * 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%
- */
-package ca.uhn.fhir.cr.dstu3;
-
-import ca.uhn.fhir.rest.api.server.RequestDetails;
-import org.opencds.cqf.fhir.cr.questionnaireresponse.dstu3.QuestionnaireResponseProcessor;
-
-@FunctionalInterface
-public interface IQuestionnaireResponseProcessorFactory {
- QuestionnaireResponseProcessor create(RequestDetails theRequestDetails);
-}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/activitydefinition/ActivityDefinitionApplyProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/activitydefinition/ActivityDefinitionApplyProvider.java
index 6940cd1410c..3bcc5beabec 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/activitydefinition/ActivityDefinitionApplyProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/activitydefinition/ActivityDefinitionApplyProvider.java
@@ -20,7 +20,7 @@ package ca.uhn.fhir.cr.dstu3.activitydefinition;
* #L%
*/
-import ca.uhn.fhir.cr.dstu3.IActivityDefinitionProcessorFactory;
+import ca.uhn.fhir.cr.common.IActivityDefinitionProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
@@ -37,13 +37,14 @@ import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ActivityDefinitionApplyProvider {
@Autowired
- IActivityDefinitionProcessorFactory myDstu3ActivityDefinitionProcessorFactory;
+ IActivityDefinitionProcessorFactory myActivityDefinitionProcessorFactory;
/**
* Implements the .
*
* @param theId The id of the PlanDefinition to apply
- * @param theCanonical The canonical identifier for the PlanDefinition to apply (optionally version-specific)
* @param thePlanDefinition The PlanDefinition to be applied
+ * @param theCanonical The canonical url of the plan definition to be applied. If the operation is invoked at the instance level, this parameter is not allowed; if the operation is invoked at the type level, this parameter (and optionally the version), or the planDefinition parameter must be supplied.
+ * @param theUrl Canonical URL of the PlanDefinition when invoked at the resource type level. This is exclusive with the planDefinition and canonical parameters.
+ * @param theVersion Version of the PlanDefinition when invoked at the resource type level. This is exclusive with the planDefinition and canonical parameters.
* @param theSubject The subject(s) that is/are the target of the plan definition to be applied.
* @param theEncounter The encounter in context
* @param thePractitioner The practitioner in context
@@ -82,8 +88,10 @@ public class PlanDefinitionApplyProvider {
@Operation(name = ProviderConstants.CR_OPERATION_APPLY, idempotent = true, type = PlanDefinition.class)
public IBaseResource apply(
@IdParam IdType theId,
+ @OperationParam(name = "planDefinition") org.hl7.fhir.r4.model.PlanDefinition thePlanDefinition,
@OperationParam(name = "canonical") String theCanonical,
- @OperationParam(name = "planDefinition") PlanDefinition thePlanDefinition,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "encounter") String theEncounter,
@OperationParam(name = "practitioner") String thePractitioner,
@@ -101,12 +109,11 @@ public class PlanDefinitionApplyProvider {
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myDstu3PlanDefinitionProcessorFactory
+ StringType canonicalType = getCanonicalType(FhirVersionEnum.DSTU3, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
.apply(
- theId,
- new StringType(theCanonical),
- thePlanDefinition,
+ Eithers.for3(canonicalType, theId, thePlanDefinition),
theSubject,
theEncounter,
thePractitioner,
@@ -117,7 +124,7 @@ public class PlanDefinitionApplyProvider {
theSetting,
theSettingContext,
theParameters,
- theUseServerData == null ? true : theUseServerData.booleanValue(),
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theData,
null,
theDataEndpoint,
@@ -127,8 +134,10 @@ public class PlanDefinitionApplyProvider {
@Operation(name = ProviderConstants.CR_OPERATION_APPLY, idempotent = true, type = PlanDefinition.class)
public IBaseResource apply(
+ @OperationParam(name = "planDefinition") org.hl7.fhir.r4.model.PlanDefinition thePlanDefinition,
@OperationParam(name = "canonical") String theCanonical,
- @OperationParam(name = "planDefinition") PlanDefinition thePlanDefinition,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "encounter") String theEncounter,
@OperationParam(name = "practitioner") String thePractitioner,
@@ -146,12 +155,11 @@ public class PlanDefinitionApplyProvider {
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myDstu3PlanDefinitionProcessorFactory
+ StringType canonicalType = getCanonicalType(FhirVersionEnum.DSTU3, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
.apply(
- null,
- new StringType(theCanonical),
- thePlanDefinition,
+ Eithers.for3(canonicalType, null, thePlanDefinition),
theSubject,
theEncounter,
thePractitioner,
@@ -162,7 +170,7 @@ public class PlanDefinitionApplyProvider {
theSetting,
theSettingContext,
theParameters,
- theUseServerData == null ? true : theUseServerData.booleanValue(),
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theData,
null,
theDataEndpoint,
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/plandefinition/PlanDefinitionPackageProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/plandefinition/PlanDefinitionPackageProvider.java
index a2666879b50..fee4cc6f240 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/plandefinition/PlanDefinitionPackageProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/plandefinition/PlanDefinitionPackageProvider.java
@@ -19,49 +19,61 @@
*/
package ca.uhn.fhir.cr.dstu3.plandefinition;
-import ca.uhn.fhir.cr.dstu3.IPlanDefinitionProcessorFactory;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.common.IPlanDefinitionProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
+import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.PlanDefinition;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
+import static ca.uhn.fhir.cr.common.CanonicalHelper.getCanonicalType;
+
public class PlanDefinitionPackageProvider {
@Autowired
- IPlanDefinitionProcessorFactory mydstu3PlanDefinitionProcessorFactory;
+ IPlanDefinitionProcessorFactory myPlanDefinitionProcessorFactory;
@Operation(name = ProviderConstants.CR_OPERATION_PACKAGE, idempotent = true, type = PlanDefinition.class)
public IBaseBundle packagePlanDefinition(
@IdParam IdType theId,
@OperationParam(name = "canonical") String theCanonical,
- @OperationParam(name = "usePut") String theIsPut,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "usePut") BooleanType theIsPut,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return mydstu3PlanDefinitionProcessorFactory
+ StringType canonicalType = getCanonicalType(FhirVersionEnum.DSTU3, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
- .packagePlanDefinition(theId, new StringType(theCanonical), null, Boolean.parseBoolean(theIsPut));
+ .packagePlanDefinition(
+ Eithers.for3(canonicalType, theId, null),
+ theIsPut == null ? Boolean.FALSE : theIsPut.booleanValue());
}
@Operation(name = ProviderConstants.CR_OPERATION_PACKAGE, idempotent = true, type = PlanDefinition.class)
public IBaseBundle packagePlanDefinition(
@OperationParam(name = "id") String theId,
@OperationParam(name = "canonical") String theCanonical,
- @OperationParam(name = "usePut") String theIsPut,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "usePut") BooleanType theIsPut,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return mydstu3PlanDefinitionProcessorFactory
+ IdType id = theId == null ? null : new IdType("PlanDefinition", theId);
+ StringType canonicalType = getCanonicalType(FhirVersionEnum.DSTU3, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
.packagePlanDefinition(
- new IdType("PlanDefinition", theId),
- new StringType(theCanonical),
- null,
- Boolean.parseBoolean(theIsPut));
+ Eithers.for3(canonicalType, id, null),
+ theIsPut == null ? Boolean.FALSE : theIsPut.booleanValue());
}
}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/questionnaire/QuestionnairePackageProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/questionnaire/QuestionnairePackageProvider.java
index 26fb7516283..1e3b33b4850 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/questionnaire/QuestionnairePackageProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/questionnaire/QuestionnairePackageProvider.java
@@ -19,7 +19,8 @@
*/
package ca.uhn.fhir.cr.dstu3.questionnaire;
-import ca.uhn.fhir.cr.dstu3.IQuestionnaireProcessorFactory;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.common.IQuestionnaireProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
@@ -29,11 +30,14 @@ import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Questionnaire;
import org.hl7.fhir.dstu3.model.StringType;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
+import static ca.uhn.fhir.cr.common.CanonicalHelper.getCanonicalType;
+
public class QuestionnairePackageProvider {
@Autowired
- IQuestionnaireProcessorFactory myDstu3QuestionnaireProcessorFactory;
+ IQuestionnaireProcessorFactory myQuestionnaireProcessorFactory;
/**
* Implements a $package operation following the Structured Data Capture (SDC) IG.
*
* @param theId The id of the Questionnaire to populate.
- * @param theCanonical The canonical identifier for the questionnaire (optionally version-specific).
* @param theQuestionnaire The Questionnaire to populate. Used when the operation is invoked at the 'type' level.
+ * @param theCanonical The canonical identifier for the questionnaire (optionally version-specific).
+ * @param theUrl Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters.
+ * @param theVersion Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters.
* @param theSubject The subject(s) that is/are the target of the Questionnaire.
* @param theParameters Any input parameters defined in libraries referenced by the Questionnaire.
- * @param theBundle Data to be made available during CQL evaluation.
+ * @param theData Data to be made available during CQL evaluation.
+ * @param theUseServerData Whether to use data from the server performing the evaluation.
* @param theDataEndpoint An endpoint to use to access data referenced by retrieve operations in libraries
* referenced by the Questionnaire.
* @param theContentEndpoint An endpoint to use to access content (i.e. libraries) referenced by the Questionnaire.
@@ -141,25 +158,28 @@ public class QuestionnairePopulateProvider {
@Operation(name = ProviderConstants.CR_OPERATION_POPULATE, idempotent = true, type = Questionnaire.class)
public QuestionnaireResponse populate(
@IdParam IdType theId,
- @OperationParam(name = "canonical") String theCanonical,
@OperationParam(name = "questionnaire") Questionnaire theQuestionnaire,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "parameters") Parameters theParameters,
- @OperationParam(name = "bundle") Bundle theBundle,
+ @OperationParam(name = "data") Bundle theData,
+ @OperationParam(name = "useServerData") BooleanType theUseServerData,
@OperationParam(name = "dataEndpoint") Endpoint theDataEndpoint,
@OperationParam(name = "contentEndpoint") Endpoint theContentEndpoint,
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return (QuestionnaireResponse) myDstu3QuestionnaireProcessorFactory
+ StringType canonicalType = getCanonicalType(FhirVersionEnum.DSTU3, theCanonical, theUrl, theVersion);
+ return (QuestionnaireResponse) myQuestionnaireProcessorFactory
.create(theRequestDetails)
.populate(
- theId,
- new StringType(theCanonical),
- theQuestionnaire,
+ Eithers.for3(canonicalType, theId, theQuestionnaire),
theSubject,
theParameters,
- theBundle,
+ theData,
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theDataEndpoint,
theContentEndpoint,
theTerminologyEndpoint);
@@ -167,25 +187,28 @@ public class QuestionnairePopulateProvider {
@Operation(name = ProviderConstants.CR_OPERATION_POPULATE, idempotent = true, type = Questionnaire.class)
public QuestionnaireResponse populate(
- @OperationParam(name = "canonical") String theCanonical,
@OperationParam(name = "questionnaire") Questionnaire theQuestionnaire,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "parameters") Parameters theParameters,
- @OperationParam(name = "bundle") Bundle theBundle,
+ @OperationParam(name = "data") Bundle theData,
+ @OperationParam(name = "useServerData") BooleanType theUseServerData,
@OperationParam(name = "dataEndpoint") Endpoint theDataEndpoint,
@OperationParam(name = "contentEndpoint") Endpoint theContentEndpoint,
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return (QuestionnaireResponse) myDstu3QuestionnaireProcessorFactory
+ StringType canonicalType = getCanonicalType(FhirVersionEnum.DSTU3, theCanonical, theUrl, theVersion);
+ return (QuestionnaireResponse) myQuestionnaireProcessorFactory
.create(theRequestDetails)
.populate(
- null,
- new StringType(theCanonical),
- theQuestionnaire,
+ Eithers.for3(canonicalType, null, theQuestionnaire),
theSubject,
theParameters,
- theBundle,
+ theData,
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theDataEndpoint,
theContentEndpoint,
theTerminologyEndpoint);
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/questionnaireresponse/QuestionnaireResponseExtractProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/questionnaireresponse/QuestionnaireResponseExtractProvider.java
index bca366e0f8e..73cd076ef7c 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/questionnaireresponse/QuestionnaireResponseExtractProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/questionnaireresponse/QuestionnaireResponseExtractProvider.java
@@ -20,31 +20,35 @@ package ca.uhn.fhir.cr.dstu3.questionnaireresponse;
* #L%
*/
-import ca.uhn.fhir.cr.dstu3.IQuestionnaireResponseProcessorFactory;
+import ca.uhn.fhir.cr.common.IQuestionnaireResponseProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
+import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.IdType;
+import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
public class QuestionnaireResponseExtractProvider {
@Autowired
- IQuestionnaireResponseProcessorFactory myDstu3QuestionnaireResponseProcessorFactory;
+ IQuestionnaireResponseProcessorFactory myQuestionnaireResponseProcessorFactory;
/**
- * Implements the $extract
+ * Implements the Structured Data Capture (SDC) IG.
*
* @param theId The id of the QuestionnaireResponse to extract data from.
* @param theQuestionnaireResponse The QuestionnaireResponse to extract data from. Used when the operation is invoked at the 'type' level.
+ * @param theParameters Any input parameters defined in libraries referenced by the Questionnaire.
+ * @param theData Data to be made available during CQL evaluation.
* @param theRequestDetails The details (such as tenant) of this request. Usually
* autopopulated HAPI.
* @return The resulting FHIR resource produced after extracting data. This will either be a single resource or a Transaction Bundle that contains multiple resources.
@@ -53,20 +57,24 @@ public class QuestionnaireResponseExtractProvider {
public IBaseBundle extract(
@IdParam IdType theId,
@OperationParam(name = "questionnaire-response") QuestionnaireResponse theQuestionnaireResponse,
+ @OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "data") Bundle theData,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myDstu3QuestionnaireResponseProcessorFactory
+ return myQuestionnaireResponseProcessorFactory
.create(theRequestDetails)
- .extract(theId, theQuestionnaireResponse, null, null, null);
+ .extract(Eithers.for2(theId, theQuestionnaireResponse), theParameters, theData);
}
@Operation(name = ProviderConstants.CR_OPERATION_EXTRACT, idempotent = true, type = QuestionnaireResponse.class)
public IBaseBundle extract(
@OperationParam(name = "questionnaire-response") QuestionnaireResponse theQuestionnaireResponse,
+ @OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "data") Bundle theData,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myDstu3QuestionnaireResponseProcessorFactory
+ return myQuestionnaireResponseProcessorFactory
.create(theRequestDetails)
- .extract(null, theQuestionnaireResponse, null, null, null);
+ .extract(Eithers.for2(null, theQuestionnaireResponse), theParameters, theData);
}
}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/structuredefinition/StructureDefinitionQuestionnaireProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/structuredefinition/StructureDefinitionQuestionnaireProvider.java
new file mode 100644
index 00000000000..a8cda85301e
--- /dev/null
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/dstu3/structuredefinition/StructureDefinitionQuestionnaireProvider.java
@@ -0,0 +1,138 @@
+/*-
+ * #%L
+ * HAPI FHIR - Clinical Reasoning
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.cr.dstu3.structuredefinition;
+
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.common.IQuestionnaireProcessorFactory;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.provider.ProviderConstants;
+import org.hl7.fhir.dstu3.model.BooleanType;
+import org.hl7.fhir.dstu3.model.Bundle;
+import org.hl7.fhir.dstu3.model.Endpoint;
+import org.hl7.fhir.dstu3.model.IdType;
+import org.hl7.fhir.dstu3.model.Parameters;
+import org.hl7.fhir.dstu3.model.Questionnaire;
+import org.hl7.fhir.dstu3.model.StringType;
+import org.hl7.fhir.dstu3.model.StructureDefinition;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static ca.uhn.fhir.cr.common.CanonicalHelper.getCanonicalType;
+
+public class StructureDefinitionQuestionnaireProvider {
+ @Autowired
+ IQuestionnaireProcessorFactory myQuestionnaireProcessorFactory;
+
+ /**
+ * Implements the $populate
+ * operation found in the
+ * Structured Data Capture (SDC) IG.
+ *
+ * @param theId The id of the StructureDefinition.
+ * @param theProfile The StructureDefinition to base the Questionnaire on. Used when the operation is invoked at the 'type' level.
+ * @param theCanonical The canonical identifier for the StructureDefinition (optionally version-specific).
+ * @param theUrl Canonical URL of the StructureDefinition when invoked at the resource type level. This is exclusive with the profile and canonical parameters.
+ * @param theVersion Version of the StructureDefinition when invoked at the resource type level. This is exclusive with the profile and canonical parameters.
+ * @param theSupportedOnly If true (default: false), the questionnaire will only include those elements marked as "mustSupport='true'" in the StructureDefinition.
+ * @param theRequiredOnly If true (default: false), the questionnaire will only include those elements marked as "min>0" in the StructureDefinition.
+ * @param theSubject The subject(s) that is/are the target of the Questionnaire.
+ * @param theParameters Any input parameters defined in libraries referenced by the StructureDefinition.
+ * @param theUseServerData Whether to use data from the server performing the evaluation.
+ * @param theData Data to be made available during CQL evaluation.
+ * @param theDataEndpoint An endpoint to use to access data referenced by retrieve operations in libraries
+ * referenced by the StructureDefinition.
+ * @param theContentEndpoint An endpoint to use to access content (i.e. libraries) referenced by the StructureDefinition.
+ * @param theTerminologyEndpoint An endpoint to use to access terminology (i.e. valuesets, codesystems, and membership testing)
+ * referenced by the StructureDefinition.
+ * @param theRequestDetails The details (such as tenant) of this request. Usually
+ * autopopulated HAPI.
+ * @return The questionnaire form generated based on the StructureDefinition.
+ */
+ @Operation(name = ProviderConstants.CR_OPERATION_QUESTIONNAIRE, idempotent = true, type = StructureDefinition.class)
+ public Questionnaire questionnaire(
+ @IdParam IdType theId,
+ @OperationParam(name = "profile") StructureDefinition theProfile,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "supportedOnly") BooleanType theSupportedOnly,
+ @OperationParam(name = "requiredOnly") BooleanType theRequiredOnly,
+ @OperationParam(name = "subject") String theSubject,
+ @OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "useServerData") BooleanType theUseServerData,
+ @OperationParam(name = "data") Bundle theData,
+ @OperationParam(name = "dataEndpoint") Endpoint theDataEndpoint,
+ @OperationParam(name = "contentEndpoint") Endpoint theContentEndpoint,
+ @OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
+ RequestDetails theRequestDetails) {
+ StringType canonicalType = getCanonicalType(FhirVersionEnum.DSTU3, theCanonical, theUrl, theVersion);
+ return (Questionnaire) myQuestionnaireProcessorFactory
+ .create(theRequestDetails)
+ .generateQuestionnaire(
+ Eithers.for3(canonicalType, theId, theProfile),
+ theSupportedOnly == null ? Boolean.TRUE : theSupportedOnly.booleanValue(),
+ theRequiredOnly == null ? Boolean.TRUE : theRequiredOnly.booleanValue(),
+ theSubject,
+ theParameters,
+ theData,
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
+ theDataEndpoint,
+ theContentEndpoint,
+ theTerminologyEndpoint,
+ null);
+ }
+
+ @Operation(name = ProviderConstants.CR_OPERATION_QUESTIONNAIRE, idempotent = true, type = StructureDefinition.class)
+ public Questionnaire questionnaire(
+ @OperationParam(name = "profile") StructureDefinition theProfile,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "supportedOnly") BooleanType theSupportedOnly,
+ @OperationParam(name = "requiredOnly") BooleanType theRequiredOnly,
+ @OperationParam(name = "subject") String theSubject,
+ @OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "useServerData") BooleanType theUseServerData,
+ @OperationParam(name = "data") Bundle theData,
+ @OperationParam(name = "dataEndpoint") Endpoint theDataEndpoint,
+ @OperationParam(name = "contentEndpoint") Endpoint theContentEndpoint,
+ @OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
+ RequestDetails theRequestDetails) {
+ StringType canonicalType = getCanonicalType(FhirVersionEnum.DSTU3, theCanonical, theUrl, theVersion);
+ return (Questionnaire) myQuestionnaireProcessorFactory
+ .create(theRequestDetails)
+ .generateQuestionnaire(
+ Eithers.for3(canonicalType, null, theProfile),
+ theSupportedOnly == null ? Boolean.TRUE : theSupportedOnly.booleanValue(),
+ theRequiredOnly == null ? Boolean.TRUE : theRequiredOnly.booleanValue(),
+ theSubject,
+ theParameters,
+ theData,
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
+ theDataEndpoint,
+ theContentEndpoint,
+ theTerminologyEndpoint,
+ null);
+ }
+}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/activitydefinition/ActivityDefinitionApplyProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/activitydefinition/ActivityDefinitionApplyProvider.java
index 2bd2c8acf5d..eefa18ce1e9 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/activitydefinition/ActivityDefinitionApplyProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/activitydefinition/ActivityDefinitionApplyProvider.java
@@ -20,7 +20,7 @@ package ca.uhn.fhir.cr.r4.activitydefinition;
* #L%
*/
-import ca.uhn.fhir.cr.r4.IActivityDefinitionProcessorFactory;
+import ca.uhn.fhir.cr.common.IActivityDefinitionProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
@@ -29,14 +29,22 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.r4.model.*;
+import org.hl7.fhir.r4.model.ActivityDefinition;
+import org.hl7.fhir.r4.model.BooleanType;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.CanonicalType;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.Endpoint;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Parameters;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ActivityDefinitionApplyProvider {
@Autowired
- IActivityDefinitionProcessorFactory myR4ActivityDefinitionProcessorFactory;
+ IActivityDefinitionProcessorFactory myActivityDefinitionProcessorFactory;
/**
* Implements the .
*
* @param theId The id of the PlanDefinition to apply
- * @param theCanonical The canonical identifier for the PlanDefinition to apply (optionally version-specific)
* @param thePlanDefinition The PlanDefinition to be applied
+ * @param theCanonical The canonical url of the plan definition to be applied. If the operation is invoked at the instance level, this parameter is not allowed; if the operation is invoked at the type level, this parameter (and optionally the version), or the planDefinition parameter must be supplied.
+ * @param theUrl Canonical URL of the PlanDefinition when invoked at the resource type level. This is exclusive with the planDefinition and canonical parameters.
+ * @param theVersion Version of the PlanDefinition when invoked at the resource type level. This is exclusive with the planDefinition and canonical parameters.
* @param theSubject The subject(s) that is/are the target of the plan definition to be applied.
* @param theEncounter The encounter in context
* @param thePractitioner The practitioner in context
@@ -77,8 +90,10 @@ public class PlanDefinitionApplyProvider {
@Operation(name = ProviderConstants.CR_OPERATION_APPLY, idempotent = true, type = PlanDefinition.class)
public IBaseResource apply(
@IdParam IdType theId,
- @OperationParam(name = "canonical") String theCanonical,
@OperationParam(name = "planDefinition") PlanDefinition thePlanDefinition,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "encounter") String theEncounter,
@OperationParam(name = "practitioner") String thePractitioner,
@@ -96,12 +111,11 @@ public class PlanDefinitionApplyProvider {
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myR4PlanDefinitionProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
.apply(
- theId,
- new CanonicalType(theCanonical),
- thePlanDefinition,
+ Eithers.for3(canonicalType, theId, thePlanDefinition),
theSubject,
theEncounter,
thePractitioner,
@@ -112,7 +126,7 @@ public class PlanDefinitionApplyProvider {
theSetting,
theSettingContext,
theParameters,
- theUseServerData == null ? true : theUseServerData.booleanValue(),
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theData,
null,
theDataEndpoint,
@@ -122,8 +136,10 @@ public class PlanDefinitionApplyProvider {
@Operation(name = ProviderConstants.CR_OPERATION_APPLY, idempotent = true, type = PlanDefinition.class)
public IBaseResource apply(
- @OperationParam(name = "canonical") String theCanonical,
@OperationParam(name = "planDefinition") PlanDefinition thePlanDefinition,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "encounter") String theEncounter,
@OperationParam(name = "practitioner") String thePractitioner,
@@ -141,12 +157,11 @@ public class PlanDefinitionApplyProvider {
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myR4PlanDefinitionProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
.apply(
- null,
- new CanonicalType(theCanonical),
- thePlanDefinition,
+ Eithers.for3(canonicalType, null, thePlanDefinition),
theSubject,
theEncounter,
thePractitioner,
@@ -157,7 +172,7 @@ public class PlanDefinitionApplyProvider {
theSetting,
theSettingContext,
theParameters,
- theUseServerData == null ? true : theUseServerData.booleanValue(),
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theData,
null,
theDataEndpoint,
@@ -175,8 +190,10 @@ public class PlanDefinitionApplyProvider {
* CPG IG. This implementation follows the R5 specification and returns a bundle of RequestGroups rather than a CarePlan.
*
* @param theId The id of the PlanDefinition to apply
- * @param theCanonical The canonical identifier for the PlanDefinition to apply (optionally version-specific)
* @param thePlanDefinition The PlanDefinition to be applied
+ * @param theCanonical The canonical url of the plan definition to be applied. If the operation is invoked at the instance level, this parameter is not allowed; if the operation is invoked at the type level, this parameter (and optionally the version), or the planDefinition parameter must be supplied.
+ * @param theUrl Canonical URL of the PlanDefinition when invoked at the resource type level. This is exclusive with the planDefinition and canonical parameters.
+ * @param theVersion Version of the PlanDefinition when invoked at the resource type level. This is exclusive with the planDefinition and canonical parameters.
* @param theSubject The subject(s) that is/are the target of the plan definition to be applied.
* @param theEncounter The encounter in context
* @param thePractitioner The practitioner in context
@@ -204,8 +221,10 @@ public class PlanDefinitionApplyProvider {
@Operation(name = ProviderConstants.CR_OPERATION_R5_APPLY, idempotent = true, type = PlanDefinition.class)
public IBaseResource applyR5(
@IdParam IdType theId,
- @OperationParam(name = "canonical") String theCanonical,
@OperationParam(name = "planDefinition") PlanDefinition thePlanDefinition,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "encounter") String theEncounter,
@OperationParam(name = "practitioner") String thePractitioner,
@@ -223,12 +242,11 @@ public class PlanDefinitionApplyProvider {
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myR4PlanDefinitionProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
.applyR5(
- theId,
- new CanonicalType(theCanonical),
- thePlanDefinition,
+ Eithers.for3(canonicalType, theId, thePlanDefinition),
theSubject,
theEncounter,
thePractitioner,
@@ -239,7 +257,7 @@ public class PlanDefinitionApplyProvider {
theSetting,
theSettingContext,
theParameters,
- theUseServerData == null ? true : theUseServerData.booleanValue(),
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theData,
null,
theDataEndpoint,
@@ -249,8 +267,10 @@ public class PlanDefinitionApplyProvider {
@Operation(name = ProviderConstants.CR_OPERATION_R5_APPLY, idempotent = true, type = PlanDefinition.class)
public IBaseResource applyR5(
- @OperationParam(name = "canonical") String theCanonical,
@OperationParam(name = "planDefinition") PlanDefinition thePlanDefinition,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "encounter") String theEncounter,
@OperationParam(name = "practitioner") String thePractitioner,
@@ -268,12 +288,11 @@ public class PlanDefinitionApplyProvider {
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myR4PlanDefinitionProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
.applyR5(
- null,
- new CanonicalType(theCanonical),
- thePlanDefinition,
+ Eithers.for3(canonicalType, null, thePlanDefinition),
theSubject,
theEncounter,
thePractitioner,
@@ -284,7 +303,7 @@ public class PlanDefinitionApplyProvider {
theSetting,
theSettingContext,
theParameters,
- theUseServerData == null ? true : theUseServerData.booleanValue(),
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theData,
null,
theDataEndpoint,
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/plandefinition/PlanDefinitionPackageProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/plandefinition/PlanDefinitionPackageProvider.java
index 8816a844b19..1edbae83f60 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/plandefinition/PlanDefinitionPackageProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/plandefinition/PlanDefinitionPackageProvider.java
@@ -19,7 +19,8 @@
*/
package ca.uhn.fhir.cr.r4.plandefinition;
-import ca.uhn.fhir.cr.r4.IPlanDefinitionProcessorFactory;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.common.IPlanDefinitionProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
@@ -28,40 +29,51 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.PlanDefinition;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
+import static ca.uhn.fhir.cr.common.CanonicalHelper.getCanonicalType;
+
public class PlanDefinitionPackageProvider {
@Autowired
- IPlanDefinitionProcessorFactory myR4PlanDefinitionProcessorFactory;
+ IPlanDefinitionProcessorFactory myPlanDefinitionProcessorFactory;
@Operation(name = ProviderConstants.CR_OPERATION_PACKAGE, idempotent = true, type = PlanDefinition.class)
public IBaseBundle packagePlanDefinition(
@IdParam IdType theId,
@OperationParam(name = "canonical") String theCanonical,
- @OperationParam(name = "usePut") String theIsPut,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "usePut") BooleanType theIsPut,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myR4PlanDefinitionProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
- .packagePlanDefinition(theId, new CanonicalType(theCanonical), null, Boolean.parseBoolean(theIsPut));
+ .packagePlanDefinition(
+ Eithers.for3(canonicalType, theId, null),
+ theIsPut == null ? Boolean.FALSE : theIsPut.booleanValue());
}
@Operation(name = ProviderConstants.CR_OPERATION_PACKAGE, idempotent = true, type = PlanDefinition.class)
public IBaseBundle packagePlanDefinition(
@OperationParam(name = "id") String theId,
@OperationParam(name = "canonical") String theCanonical,
- @OperationParam(name = "usePut") String theIsPut,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "usePut") BooleanType theIsPut,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myR4PlanDefinitionProcessorFactory
+ IdType id = theId == null ? null : new IdType("PlanDefinition", theId);
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return myPlanDefinitionProcessorFactory
.create(theRequestDetails)
.packagePlanDefinition(
- new IdType("PlanDefinition", theId),
- new CanonicalType(theCanonical),
- null,
- Boolean.parseBoolean(theIsPut));
+ Eithers.for3(canonicalType, id, null),
+ theIsPut == null ? Boolean.FALSE : theIsPut.booleanValue());
}
}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaire/QuestionnairePackageProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaire/QuestionnairePackageProvider.java
index f00d398bc51..94599d0098a 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaire/QuestionnairePackageProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaire/QuestionnairePackageProvider.java
@@ -19,29 +19,36 @@
*/
package ca.uhn.fhir.cr.r4.questionnaire;
-import ca.uhn.fhir.cr.r4.IQuestionnaireProcessorFactory;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.common.IQuestionnaireProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
+import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Questionnaire;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
+import static ca.uhn.fhir.cr.common.CanonicalHelper.getCanonicalType;
+
public class QuestionnairePackageProvider {
@Autowired
- IQuestionnaireProcessorFactory myR4QuestionnaireProcessorFactory;
+ IQuestionnaireProcessorFactory myQuestionnaireProcessorFactory;
/**
* Implements a $package operation following the CRMI IG.
*
* @param theId The id of the Questionnaire.
- * @param theCanonical The canonical identifier for the questionnaire (optionally version-specific).
- * @Param theIsPut A boolean value to determine if the Bundle returned uses PUT or POST request methods. Defaults to false.
+ * @param theCanonical The canonical identifier for the Questionnaire (optionally version-specific).
+ * @param theUrl Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters.
+ * @param theVersion Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters.
+ * @Param theIsPut A boolean value to determine if the Bundle returned uses PUT or POST request methods. Defaults to false.
* @param theRequestDetails The details (such as tenant) of this request. Usually
* autopopulated by HAPI.
* @return A Bundle containing the Questionnaire and all related Library, CodeSystem and ValueSet resources
@@ -50,20 +57,30 @@ public class QuestionnairePackageProvider {
public Bundle packageQuestionnaire(
@IdParam IdType theId,
@OperationParam(name = "canonical") String theCanonical,
- @OperationParam(name = "usePut") String theIsPut,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "usePut") BooleanType theIsPut,
RequestDetails theRequestDetails) {
- return (Bundle) myR4QuestionnaireProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return (Bundle) myQuestionnaireProcessorFactory
.create(theRequestDetails)
- .packageQuestionnaire(theId, new CanonicalType(theCanonical), null, Boolean.parseBoolean(theIsPut));
+ .packageQuestionnaire(
+ Eithers.for3(canonicalType, theId, null),
+ theIsPut == null ? Boolean.FALSE : theIsPut.booleanValue());
}
@Operation(name = ProviderConstants.CR_OPERATION_PACKAGE, idempotent = true, type = Questionnaire.class)
public Bundle packageQuestionnaire(
@OperationParam(name = "canonical") String theCanonical,
- @OperationParam(name = "usePut") String theIsPut,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "usePut") BooleanType theIsPut,
RequestDetails theRequestDetails) {
- return (Bundle) myR4QuestionnaireProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return (Bundle) myQuestionnaireProcessorFactory
.create(theRequestDetails)
- .packageQuestionnaire(null, new CanonicalType(theCanonical), null, Boolean.parseBoolean(theIsPut));
+ .packageQuestionnaire(
+ Eithers.for3(canonicalType, null, null),
+ theIsPut == null ? Boolean.FALSE : theIsPut.booleanValue());
}
}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaire/QuestionnairePopulateProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaire/QuestionnairePopulateProvider.java
index 0108c050fa3..4346fb25a46 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaire/QuestionnairePopulateProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaire/QuestionnairePopulateProvider.java
@@ -20,7 +20,8 @@ package ca.uhn.fhir.cr.r4.questionnaire;
* #L%
*/
-import ca.uhn.fhir.cr.r4.IQuestionnaireProcessorFactory;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.common.IQuestionnaireProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
@@ -28,6 +29,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.Endpoint;
@@ -35,11 +37,14 @@ import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
+import static ca.uhn.fhir.cr.common.CanonicalHelper.getCanonicalType;
+
public class QuestionnairePopulateProvider {
@Autowired
- IQuestionnaireProcessorFactory myR4QuestionnaireProcessorFactory;
+ IQuestionnaireProcessorFactory myQuestionnaireProcessorFactory;
/**
* Implements a modified version of the Structured Data Capture (SDC) IG.
*
* @param theId The id of the Questionnaire to populate.
- * @param theCanonical The canonical identifier for the questionnaire (optionally version-specific).
* @param theQuestionnaire The Questionnaire to populate. Used when the operation is invoked at the 'type' level.
+ * @param theCanonical The canonical identifier for the questionnaire (optionally version-specific).
+ * @param theUrl Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters.
+ * @param theVersion Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters.
* @param theSubject The subject(s) that is/are the target of the Questionnaire.
- * @param theParameters Any input parameters defined in libraries referenced by the Questionnaire.
- * @param theBundle Data to be made available during CQL evaluation.
+ * @param theUseServerData Whether to use data from the server performing the evaluation.
+ * @param theData Data to be made available during CQL evaluation.
+ * @param theBundle Legacy support for data parameter.
* @param theDataEndpoint An endpoint to use to access data referenced by retrieve operations in libraries
* referenced by the Questionnaire.
* @param theContentEndpoint An endpoint to use to access content (i.e. libraries) referenced by the Questionnaire.
@@ -141,25 +163,30 @@ public class QuestionnairePopulateProvider {
@Operation(name = ProviderConstants.CR_OPERATION_POPULATE, idempotent = true, type = Questionnaire.class)
public QuestionnaireResponse populate(
@IdParam IdType theId,
- @OperationParam(name = "canonical") String theCanonical,
@OperationParam(name = "questionnaire") Questionnaire theQuestionnaire,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "useServerData") BooleanType theUseServerData,
+ @OperationParam(name = "data") Bundle theData,
@OperationParam(name = "bundle") Bundle theBundle,
@OperationParam(name = "dataEndpoint") Endpoint theDataEndpoint,
@OperationParam(name = "contentEndpoint") Endpoint theContentEndpoint,
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return (QuestionnaireResponse) myR4QuestionnaireProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ Bundle data = theData == null ? theBundle : theData;
+ return (QuestionnaireResponse) myQuestionnaireProcessorFactory
.create(theRequestDetails)
.populate(
- theId,
- new CanonicalType(theCanonical),
- theQuestionnaire,
+ Eithers.for3(canonicalType, theId, theQuestionnaire),
theSubject,
theParameters,
- theBundle,
+ data,
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theDataEndpoint,
theContentEndpoint,
theTerminologyEndpoint);
@@ -167,25 +194,30 @@ public class QuestionnairePopulateProvider {
@Operation(name = ProviderConstants.CR_OPERATION_POPULATE, idempotent = true, type = Questionnaire.class)
public QuestionnaireResponse populate(
- @OperationParam(name = "canonical") String theCanonical,
@OperationParam(name = "questionnaire") Questionnaire theQuestionnaire,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
@OperationParam(name = "subject") String theSubject,
@OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "useServerData") BooleanType theUseServerData,
+ @OperationParam(name = "data") Bundle theData,
@OperationParam(name = "bundle") Bundle theBundle,
@OperationParam(name = "dataEndpoint") Endpoint theDataEndpoint,
@OperationParam(name = "contentEndpoint") Endpoint theContentEndpoint,
@OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return (QuestionnaireResponse) myR4QuestionnaireProcessorFactory
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ Bundle data = theData == null ? theBundle : theData;
+ return (QuestionnaireResponse) myQuestionnaireProcessorFactory
.create(theRequestDetails)
.populate(
- null,
- new CanonicalType(theCanonical),
- theQuestionnaire,
+ Eithers.for3(canonicalType, null, theQuestionnaire),
theSubject,
theParameters,
- theBundle,
+ data,
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
theDataEndpoint,
theContentEndpoint,
theTerminologyEndpoint);
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaireresponse/QuestionnaireResponseExtractProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaireresponse/QuestionnaireResponseExtractProvider.java
index f7598e66487..65242192998 100644
--- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaireresponse/QuestionnaireResponseExtractProvider.java
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/questionnaireresponse/QuestionnaireResponseExtractProvider.java
@@ -20,7 +20,7 @@ package ca.uhn.fhir.cr.r4.questionnaireresponse;
* #L%
*/
-import ca.uhn.fhir.cr.r4.IQuestionnaireResponseProcessorFactory;
+import ca.uhn.fhir.cr.common.IQuestionnaireResponseProcessorFactory;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
@@ -29,22 +29,26 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.springframework.beans.factory.annotation.Autowired;
public class QuestionnaireResponseExtractProvider {
@Autowired
- IQuestionnaireResponseProcessorFactory myR4QuestionnaireResponseProcessorFactory;
+ IQuestionnaireResponseProcessorFactory myQuestionnaireResponseProcessorFactory;
/**
- * Implements the $extract
+ * Implements the Structured Data Capture (SDC) IG.
*
* @param theId The id of the QuestionnaireResponse to extract data from.
* @param theQuestionnaireResponse The QuestionnaireResponse to extract data from. Used when the operation is invoked at the 'type' level.
+ * @param theParameters Any input parameters defined in libraries referenced by the Questionnaire.
+ * @param theData Data to be made available during CQL evaluation.
* @param theRequestDetails The details (such as tenant) of this request. Usually
* autopopulated HAPI.
* @return The resulting FHIR resource produced after extracting data. This will either be a single resource or a Transaction Bundle that contains multiple resources.
@@ -53,20 +57,24 @@ public class QuestionnaireResponseExtractProvider {
public IBaseBundle extract(
@IdParam IdType theId,
@OperationParam(name = "questionnaire-response") QuestionnaireResponse theQuestionnaireResponse,
+ @OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "data") Bundle theData,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myR4QuestionnaireResponseProcessorFactory
+ return myQuestionnaireResponseProcessorFactory
.create(theRequestDetails)
- .extract(theId, theQuestionnaireResponse, null, null, null);
+ .extract(Eithers.for2(theId, theQuestionnaireResponse), theParameters, theData);
}
@Operation(name = ProviderConstants.CR_OPERATION_EXTRACT, idempotent = true, type = QuestionnaireResponse.class)
public IBaseBundle extract(
@OperationParam(name = "questionnaire-response") QuestionnaireResponse theQuestionnaireResponse,
+ @OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "data") Bundle theData,
RequestDetails theRequestDetails)
throws InternalErrorException, FHIRException {
- return myR4QuestionnaireResponseProcessorFactory
+ return myQuestionnaireResponseProcessorFactory
.create(theRequestDetails)
- .extract(null, theQuestionnaireResponse, null, null, null);
+ .extract(Eithers.for2(null, theQuestionnaireResponse), theParameters, theData);
}
}
diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/structuredefinition/StructureDefinitionQuestionnaireProvider.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/structuredefinition/StructureDefinitionQuestionnaireProvider.java
new file mode 100644
index 00000000000..f125db8828f
--- /dev/null
+++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/r4/structuredefinition/StructureDefinitionQuestionnaireProvider.java
@@ -0,0 +1,137 @@
+/*-
+ * #%L
+ * HAPI FHIR - Clinical Reasoning
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.cr.r4.structuredefinition;
+
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.cr.common.IQuestionnaireProcessorFactory;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.provider.ProviderConstants;
+import org.hl7.fhir.r4.model.BooleanType;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.CanonicalType;
+import org.hl7.fhir.r4.model.Endpoint;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Parameters;
+import org.hl7.fhir.r4.model.Questionnaire;
+import org.hl7.fhir.r4.model.StructureDefinition;
+import org.opencds.cqf.fhir.utility.monad.Eithers;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static ca.uhn.fhir.cr.common.CanonicalHelper.getCanonicalType;
+
+public class StructureDefinitionQuestionnaireProvider {
+ @Autowired
+ IQuestionnaireProcessorFactory myQuestionnaireProcessorFactory;
+
+ /**
+ * Implements the $questionnaire
+ * operation.
+ *
+ * @param theId The id of the StructureDefinition.
+ * @param theProfile The StructureDefinition to base the Questionnaire on. Used when the operation is invoked at the 'type' level.
+ * @param theCanonical The canonical identifier for the StructureDefinition (optionally version-specific).
+ * @param theUrl Canonical URL of the StructureDefinition when invoked at the resource type level. This is exclusive with the profile and canonical parameters.
+ * @param theVersion Version of the StructureDefinition when invoked at the resource type level. This is exclusive with the profile and canonical parameters.
+ * @param theSupportedOnly If true (default: false), the questionnaire will only include those elements marked as "mustSupport='true'" in the StructureDefinition.
+ * @param theRequiredOnly If true (default: false), the questionnaire will only include those elements marked as "min>0" in the StructureDefinition.
+ * @param theSubject The subject(s) that is/are the target of the Questionnaire.
+ * @param theParameters Any input parameters defined in libraries referenced by the StructureDefinition.
+ * @param theUseServerData Whether to use data from the server performing the evaluation.
+ * @param theData Data to be made available during CQL evaluation.
+ * @param theDataEndpoint An endpoint to use to access data referenced by retrieve operations in libraries
+ * referenced by the StructureDefinition.
+ * @param theContentEndpoint An endpoint to use to access content (i.e. libraries) referenced by the StructureDefinition.
+ * @param theTerminologyEndpoint An endpoint to use to access terminology (i.e. valuesets, codesystems, and membership testing)
+ * referenced by the StructureDefinition.
+ * @param theRequestDetails The details (such as tenant) of this request. Usually
+ * autopopulated HAPI.
+ * @return The questionnaire form generated based on the StructureDefinition.
+ */
+ @Operation(name = ProviderConstants.CR_OPERATION_QUESTIONNAIRE, idempotent = true, type = StructureDefinition.class)
+ public Questionnaire questionnaire(
+ @IdParam IdType theId,
+ @OperationParam(name = "profile") StructureDefinition theProfile,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "supportedOnly") BooleanType theSupportedOnly,
+ @OperationParam(name = "requiredOnly") BooleanType theRequiredOnly,
+ @OperationParam(name = "subject") String theSubject,
+ @OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "useServerData") BooleanType theUseServerData,
+ @OperationParam(name = "data") Bundle theData,
+ @OperationParam(name = "dataEndpoint") Endpoint theDataEndpoint,
+ @OperationParam(name = "contentEndpoint") Endpoint theContentEndpoint,
+ @OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
+ RequestDetails theRequestDetails) {
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return (Questionnaire) myQuestionnaireProcessorFactory
+ .create(theRequestDetails)
+ .generateQuestionnaire(
+ Eithers.for3(canonicalType, theId, theProfile),
+ theSupportedOnly == null ? Boolean.FALSE : theSupportedOnly.booleanValue(),
+ theRequiredOnly == null ? Boolean.FALSE : theRequiredOnly.booleanValue(),
+ theSubject,
+ theParameters,
+ theData,
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
+ theDataEndpoint,
+ theContentEndpoint,
+ theTerminologyEndpoint,
+ null);
+ }
+
+ @Operation(name = ProviderConstants.CR_OPERATION_QUESTIONNAIRE, idempotent = true, type = StructureDefinition.class)
+ public Questionnaire questionnaire(
+ @OperationParam(name = "profile") StructureDefinition theProfile,
+ @OperationParam(name = "canonical") String theCanonical,
+ @OperationParam(name = "url") String theUrl,
+ @OperationParam(name = "version") String theVersion,
+ @OperationParam(name = "supportedOnly") BooleanType theSupportedOnly,
+ @OperationParam(name = "requiredOnly") BooleanType theRequiredOnly,
+ @OperationParam(name = "subject") String theSubject,
+ @OperationParam(name = "parameters") Parameters theParameters,
+ @OperationParam(name = "useServerData") BooleanType theUseServerData,
+ @OperationParam(name = "data") Bundle theData,
+ @OperationParam(name = "dataEndpoint") Endpoint theDataEndpoint,
+ @OperationParam(name = "contentEndpoint") Endpoint theContentEndpoint,
+ @OperationParam(name = "terminologyEndpoint") Endpoint theTerminologyEndpoint,
+ RequestDetails theRequestDetails) {
+ CanonicalType canonicalType = getCanonicalType(FhirVersionEnum.R4, theCanonical, theUrl, theVersion);
+ return (Questionnaire) myQuestionnaireProcessorFactory
+ .create(theRequestDetails)
+ .generateQuestionnaire(
+ Eithers.for3(canonicalType, null, theProfile),
+ theSupportedOnly == null ? Boolean.FALSE : theSupportedOnly.booleanValue(),
+ theRequiredOnly == null ? Boolean.FALSE : theRequiredOnly.booleanValue(),
+ theSubject,
+ theParameters,
+ theData,
+ theUseServerData == null ? Boolean.TRUE : theUseServerData.booleanValue(),
+ theDataEndpoint,
+ theContentEndpoint,
+ theTerminologyEndpoint,
+ null);
+ }
+}
diff --git a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/PlanDefinitionOperationsProviderTest.java b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/PlanDefinitionOperationsProviderTest.java
index ef53d8acc57..7beb1e5a208 100644
--- a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/PlanDefinitionOperationsProviderTest.java
+++ b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/PlanDefinitionOperationsProviderTest.java
@@ -19,10 +19,11 @@ public class PlanDefinitionOperationsProviderTest extends BaseCrR4TestServer {
loadBundle("ca/uhn/fhir/cr/r4/Bundle-PatientData.json");
var requestDetails = setupRequestDetails();
- var planDefinitionID = new IdType(Enumerations.FHIRAllTypes.PLANDEFINITION.toCode(), "ASLPA1");
+ var url = "http://example.org/sdh/dtr/aslp/PlanDefinition/ASLPA1";
+ var version = "1.0.0";
var patientID = "positive";
var parameters = new Parameters().addParameter("Service Request Id", "SleepStudy").addParameter("Service Request Id", "SleepStudy2");
- var result = (CarePlan) myPlanDefinitionApplyProvider.apply(planDefinitionID, null, null, patientID,
+ var result = (CarePlan) myPlanDefinitionApplyProvider.apply(null, null, null, url, version, patientID,
null, null, null, null, null,
null, null, null, parameters, new BooleanType(true), null,
null, null, null,
@@ -35,7 +36,7 @@ public class PlanDefinitionOperationsProviderTest extends BaseCrR4TestServer {
.getItem().get(0)
.getText());
- var resultR5 = (Bundle) myPlanDefinitionApplyProvider.applyR5(planDefinitionID, null, null, patientID,
+ var resultR5 = (Bundle) myPlanDefinitionApplyProvider.applyR5(null, null, null, url, version, patientID,
null, null, null, null, null,
null, null, null, parameters, new BooleanType(true), null,
null, null, null,
diff --git a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/QuestionnaireOperationsProviderTest.java b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/QuestionnaireOperationsProviderTest.java
index 353539f91d6..f14507d6a52 100644
--- a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/QuestionnaireOperationsProviderTest.java
+++ b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/QuestionnaireOperationsProviderTest.java
@@ -3,6 +3,7 @@ package ca.uhn.fhir.cr.r4;
import ca.uhn.fhir.cr.r4.questionnaire.QuestionnairePackageProvider;
import ca.uhn.fhir.cr.r4.questionnaire.QuestionnairePopulateProvider;
+import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
@@ -28,7 +29,7 @@ public class QuestionnaireOperationsProviderTest extends BaseCrR4TestServer {
var theSubject = "positive";
var parameters = new Parameters().addParameter("Service Request Id", "SleepStudy").addParameter("Service Request Id", "SleepStudy2");
var result = myQuestionnairePopulateProvider.populate(new IdType("Questionnaire", "ASLPA1"),
- null, null, theSubject, parameters,
+ null, null, null, null, theSubject, parameters, null, null,
null, null, null, null,
requestDetails);
@@ -45,7 +46,7 @@ public class QuestionnaireOperationsProviderTest extends BaseCrR4TestServer {
var theSubject = "positive";
var parameters = new Parameters().addParameter("Service Request Id", "SleepStudy").addParameter("Service Request Id", "SleepStudy2");
var result = myQuestionnairePopulateProvider.prepopulate(new IdType("Questionnaire", "ASLPA1"),
- null, null, theSubject, parameters,
+ null, null, null, null, theSubject, parameters, null, null,
null, null, null, null,
requestDetails);
@@ -58,7 +59,7 @@ public class QuestionnaireOperationsProviderTest extends BaseCrR4TestServer {
loadBundle("ca/uhn/fhir/cr/r4/Bundle-QuestionnairePackage.json");
var requestDetails = setupRequestDetails();
var result = myQuestionnairePackageProvider.packageQuestionnaire(null,
- "http://example.org/sdh/dtr/aslp/Questionnaire/ASLPA1", "true",
+ "http://example.org/sdh/dtr/aslp/Questionnaire/ASLPA1", null, null, new BooleanType("true"),
requestDetails);
assertNotNull(result);
diff --git a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/QuestionnaireResponseOperationsProviderTest.java b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/QuestionnaireResponseOperationsProviderTest.java
index 334c551127c..0d21df7dd94 100644
--- a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/QuestionnaireResponseOperationsProviderTest.java
+++ b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/QuestionnaireResponseOperationsProviderTest.java
@@ -21,7 +21,7 @@ public class QuestionnaireResponseOperationsProviderTest extends BaseCrR4TestSer
var requestDetails = setupRequestDetails();
loadResource(Questionnaire.class, "ca/uhn/fhir/cr/r4/Questionnaire-MyPainQuestionnaire.json", requestDetails);
var questionnaireResponse = readResource(QuestionnaireResponse.class, "ca/uhn/fhir/cr/r4/QuestionnaireResponse-QRSharonDecision.json");
- var result = (Bundle) myQuestionnaireResponseExtractProvider.extract(null, questionnaireResponse, requestDetails);
+ var result = (Bundle) myQuestionnaireResponseExtractProvider.extract(null, questionnaireResponse, null, null, requestDetails);
assertNotNull(result);
assertEquals(5, result.getEntry().size());
diff --git a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/TestCrR4Config.java b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/TestCrR4Config.java
index c6efed8ddee..e142b5e7c7d 100644
--- a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/TestCrR4Config.java
+++ b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/TestCrR4Config.java
@@ -49,7 +49,6 @@ public class TestCrR4Config {
@Bean
CareGapsProperties careGapsProperties() {
var careGapsProperties = new CareGapsProperties();
- careGapsProperties.setThreadedCareGapsEnabled(false);
careGapsProperties.setCareGapsReporter("Organization/alphora");
careGapsProperties.setCareGapsCompositionSectionAuthor("Organization/alphora-author");
return careGapsProperties;
diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml
index d38f71d841c..aaaffd4dfe4 100644
--- a/hapi-fhir-storage-mdm/pom.xml
+++ b/hapi-fhir-storage-mdm/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml
index ab081e7bd27..97697fbeceb 100644
--- a/hapi-fhir-storage-test-utilities/pom.xml
+++ b/hapi-fhir-storage-test-utilities/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml
index 804610dd522..96682b6343b 100644
--- a/hapi-fhir-storage/pom.xml
+++ b/hapi-fhir-storage/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java
index 2c86b83a33c..294b72da983 100644
--- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java
+++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java
@@ -49,6 +49,10 @@ import java.util.TreeSet;
@SuppressWarnings("JavadocLinkAsPlainText")
public class JpaStorageSettings extends StorageSettings {
+ /**
+ * Default value for {@link #getBulkExportFileMaximumSize()}: 100 MB
+ */
+ public static final long DEFAULT_BULK_EXPORT_MAXIMUM_WORK_CHUNK_SIZE = 100 * FileUtils.ONE_MB;
/**
* Default value for {@link #setReuseCachedSearchResultsForMillis(Long)}: 60000ms (one minute)
*/
@@ -313,6 +317,10 @@ public class JpaStorageSettings extends StorageSettings {
* Since 6.2.0
*/
private int myBulkExportFileMaximumCapacity = DEFAULT_BULK_EXPORT_FILE_MAXIMUM_CAPACITY;
+ /**
+ * Since 7.2.0
+ */
+ private long myBulkExportFileMaximumSize = DEFAULT_BULK_EXPORT_MAXIMUM_WORK_CHUNK_SIZE;
/**
* Since 6.4.0
*/
@@ -2301,11 +2309,42 @@ public class JpaStorageSettings extends StorageSettings {
* Default is 1000 resources per file.
*
* @since 6.2.0
+ * @see #setBulkExportFileMaximumCapacity(int)
*/
public void setBulkExportFileMaximumCapacity(int theBulkExportFileMaximumCapacity) {
myBulkExportFileMaximumCapacity = theBulkExportFileMaximumCapacity;
}
+ /**
+ * Defines the maximum size for a single work chunk or report file to be held in
+ * memory or stored in the database for bulk export jobs.
+ * Note that the framework will attempt to not exceed this limit, but will only
+ * estimate the actual chunk size as it works, so this value should be set
+ * below any hard limits that may be present.
+ *
+ * @since 7.2.0
+ * @see #DEFAULT_BULK_EXPORT_MAXIMUM_WORK_CHUNK_SIZE The default value for this setting
+ */
+ public long getBulkExportFileMaximumSize() {
+ return myBulkExportFileMaximumSize;
+ }
+
+ /**
+ * Defines the maximum size for a single work chunk or report file to be held in
+ * memory or stored in the database for bulk export jobs. Default is 100 MB.
+ * Note that the framework will attempt to not exceed this limit, but will only
+ * estimate the actual chunk size as it works, so this value should be set
+ * below any hard limits that may be present.
+ *
+ * @since 7.2.0
+ * @see #setBulkExportFileMaximumCapacity(int)
+ * @see #DEFAULT_BULK_EXPORT_MAXIMUM_WORK_CHUNK_SIZE The default value for this setting
+ */
+ public void setBulkExportFileMaximumSize(long theBulkExportFileMaximumSize) {
+ Validate.isTrue(theBulkExportFileMaximumSize > 0, "theBulkExportFileMaximumSize must be positive");
+ myBulkExportFileMaximumSize = theBulkExportFileMaximumSize;
+ }
+
/**
* If this setting is enabled, then gated batch jobs that produce only one chunk will immediately trigger a batch
* maintenance job. This may be useful for testing, but is not recommended for production use.
diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/patch/FhirPatch.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/patch/FhirPatch.java
index 270a0c002af..3fd0c7ba915 100644
--- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/patch/FhirPatch.java
+++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/patch/FhirPatch.java
@@ -30,10 +30,10 @@ import ca.uhn.fhir.util.IModelVisitor2;
import ca.uhn.fhir.util.ParametersUtil;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
+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.IBaseEnumeration;
-import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@@ -49,7 +49,6 @@ import java.util.Optional;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.defaultString;
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirPatch {
@@ -319,12 +318,9 @@ public class FhirPatch {
if (valuePartValue.isPresent()) {
newValue = valuePartValue.get();
} else {
- newValue = theChildDefinition.getChildElement().newInstance();
+ List partParts = valuePart.map(this::extractPartsFromPart).orElse(Collections.emptyList());
- if (valuePart.isPresent()) {
- IBase theValueElement = valuePart.get();
- populateNewValue(theChildDefinition, newValue, theValueElement);
- }
+ newValue = createAndPopulateNewElement(theChildDefinition, partParts);
}
if (IBaseEnumeration.class.isAssignableFrom(
@@ -350,31 +346,65 @@ public class FhirPatch {
return newValue;
}
- private void populateNewValue(ChildDefinition theChildDefinition, IBase theNewValue, IBase theValueElement) {
- List valuePartParts = myContext.newTerser().getValues(theValueElement, "part");
- for (IBase nextValuePartPart : valuePartParts) {
+ @Nonnull
+ private List extractPartsFromPart(IBase theParametersParameterComponent) {
+ return myContext.newTerser().getValues(theParametersParameterComponent, "part");
+ }
+
+ /**
+ * this method will instantiate an element according to the provided Definition and it according to
+ * the properties found in thePartParts. a part usually represent a datatype as a name/value[X] pair.
+ * it may also represent a complex type like an Extension.
+ *
+ * @param theDefinition wrapper around the runtime definition of the element to be populated
+ * @param thePartParts list of Part to populate the element that will be created from theDefinition
+ * @return an element that was created from theDefinition and populated with the parts
+ */
+ private IBase createAndPopulateNewElement(ChildDefinition theDefinition, List thePartParts) {
+ IBase newElement = theDefinition.getChildElement().newInstance();
+
+ for (IBase nextValuePartPart : thePartParts) {
String name = myContext
.newTerser()
.getSingleValue(nextValuePartPart, PARAMETER_NAME, IPrimitiveType.class)
.map(IPrimitiveType::getValueAsString)
.orElse(null);
- if (isNotBlank(name)) {
- Optional value =
- myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class);
- if (value.isPresent()) {
+ if (StringUtils.isBlank(name)) {
+ continue;
+ }
- BaseRuntimeChildDefinition partChildDef =
- theChildDefinition.getChildElement().getChildByName(name);
- if (partChildDef == null) {
- name = name + "[x]";
- partChildDef = theChildDefinition.getChildElement().getChildByName(name);
- }
- partChildDef.getMutator().addValue(theNewValue, value.get());
+ Optional value = myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class);
+
+ if (value.isPresent()) {
+ // we have a dataType. let's extract its value and assign it.
+ BaseRuntimeChildDefinition partChildDef =
+ theDefinition.getChildElement().getChildByName(name);
+ if (partChildDef == null) {
+ name = name + "[x]";
+ partChildDef = theDefinition.getChildElement().getChildByName(name);
}
+ partChildDef.getMutator().addValue(newElement, value.get());
+
+ // a part represent a datatype or a complexType but not both at the same time.
+ continue;
+ }
+
+ List part = extractPartsFromPart(nextValuePartPart);
+
+ if (!part.isEmpty()) {
+ // we have a complexType. let's find its definition and recursively process
+ // them till all complexTypes are processed.
+ ChildDefinition childDefinition = findChildDefinition(newElement, name);
+
+ IBase childNewValue = createAndPopulateNewElement(childDefinition, part);
+
+ childDefinition.getChildDef().getMutator().setValue(newElement, childNewValue);
}
}
+
+ return newElement;
}
private void deleteSingleElement(IBase theElementToDelete) {
@@ -390,17 +420,6 @@ public class FhirPatch {
}
return true;
}
-
- @Override
- public boolean acceptUndeclaredExtension(
- IBaseExtension, ?> theNextExt,
- List theContainingElementPath,
- List theChildDefinitionPath,
- List> theElementDefinitionPath) {
- theNextExt.setUrl(null);
- theNextExt.setValue(null);
- return true;
- }
});
}
@@ -565,7 +584,7 @@ public class FhirPatch {
* If the value is a Resource or a datatype, we can put it into the part.value and that will cover
* all of its children. If it's an infrastructure element though, such as Patient.contact we can't
* just put it into part.value because it isn't an actual type. So we have to put all of its
- * childen in instead.
+ * children in instead.
*/
if (valueDef.isStandardType()) {
ParametersUtil.addPart(myContext, operation, PARAMETER_VALUE, value);
diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml
index ec487f310cb..541a22ad700 100644
--- a/hapi-fhir-structures-dstu2.1/pom.xml
+++ b/hapi-fhir-structures-dstu2.1/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml
index 32b46056807..0dab5394bcd 100644
--- a/hapi-fhir-structures-dstu2/pom.xml
+++ b/hapi-fhir-structures-dstu2/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml
index eb66657106a..fc3ad9f5c7f 100644
--- a/hapi-fhir-structures-dstu3/pom.xml
+++ b/hapi-fhir-structures-dstu3/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml
index 9b32e4e252f..04e09044793 100644
--- a/hapi-fhir-structures-hl7org-dstu2/pom.xml
+++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml
index 37f6865be6b..b4150ec9c38 100644
--- a/hapi-fhir-structures-r4/pom.xml
+++ b/hapi-fhir-structures-r4/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java
index 64fdf393cca..1dead512c7f 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java
@@ -2,14 +2,13 @@ package ca.uhn.fhir.rest.param;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
-import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
public class TokenParamTest {
private static final FhirContext ourCtx = FhirContext.forR4Cached();
@@ -51,19 +50,16 @@ public class TokenParamTest {
}
@Test
- public void testNameNickname() {
- StringParam param = new StringParam();
- assertFalse(param.isNicknameExpand());
- param.setValueAsQueryToken(ourCtx, "name", Constants.PARAMQUALIFIER_NICKNAME, "kenny");
- assertTrue(param.isNicknameExpand());
+ public void testMdmQualifier() {
+ final String value = "Patient/PJANE1";
+
+ TokenParam param = new TokenParam();
+ param.setValueAsQueryToken(ourCtx, "_id", Constants.PARAMQUALIFIER_MDM, value);
+ assertNull(param.getModifier());
+ assertNull(param.getSystem());
+ assertTrue(param.isMdmExpand());
+ assertEquals(value, param.getValue());
}
- @Test
- public void testGivenNickname() {
- StringParam param = new StringParam();
- assertFalse(param.isNicknameExpand());
- param.setValueAsQueryToken(ourCtx, "given", Constants.PARAMQUALIFIER_NICKNAME, "kenny");
- assertTrue(param.isNicknameExpand());
- }
}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/NullMethodOutcomeResourceProviderTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/NullMethodOutcomeResourceProviderTest.java
new file mode 100644
index 00000000000..3a784d82d00
--- /dev/null
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/NullMethodOutcomeResourceProviderTest.java
@@ -0,0 +1,125 @@
+package ca.uhn.fhir.rest.server;
+
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.Delete;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Patch;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.annotation.Validate;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PatchTypeEnum;
+import ca.uhn.fhir.rest.client.api.IGenericClient;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
+import org.apache.http.HttpStatus;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Parameters;
+import org.hl7.fhir.r4.model.Patient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class NullMethodOutcomeResourceProviderTest {
+
+ public static final String TEST_PATIENT_ID = "Patient/123";
+
+ @RegisterExtension
+ private RestfulServerExtension myServer = new RestfulServerExtension(FhirVersionEnum.R4)
+ .registerProvider(new NullMethodOutcomePatientProvider());
+
+ private IGenericClient myClient;
+ private Patient myPatient;
+
+ @BeforeEach
+ public void before() {
+ myPatient = new Patient();
+ myClient = myServer.getFhirClient();
+ }
+
+ @Test
+ public void testCreate_withNullMethodOutcome_throwsException() {
+ try {
+ myClient.create().resource(myPatient).execute();
+ fail();
+ } catch (InternalErrorException e){
+ assertTrue(e.getMessage().contains("HTTP 500 Server Error: HAPI-0368"));
+ }
+ }
+
+ @Test
+ public void testUpdate_withNullMethodOutcome_returnsHttp200() {
+ myPatient.setId(TEST_PATIENT_ID);
+ MethodOutcome outcome = myClient.update().resource(myPatient).execute();
+ assertEquals(HttpStatus.SC_OK, outcome.getResponseStatusCode());
+ }
+
+ @Test
+ public void testPatch_withNullMethodOutcome_returnsHttp200() {
+ MethodOutcome outcome = myClient.patch().withFhirPatch(new Parameters()).withId(TEST_PATIENT_ID).execute();
+ assertEquals(HttpStatus.SC_OK, outcome.getResponseStatusCode());
+ }
+
+ @Test
+ public void testValidate_withNullMethodOutcome_throwsException() {
+ try {
+ myClient.validate().resource(myPatient).execute();
+ fail();
+ } catch (ResourceNotFoundException e){
+ // This fails with HAPI-0436 because the MethodOutcome of the @Validate method is used
+ // to build an IBundleProvider with a OperationOutcome resource (which will be null from the provider below).
+ // See OperationMethodBinding#invokeServer()
+ assertTrue(e.getMessage().contains("HTTP 404 Not Found: HAPI-0436"));
+ }
+ }
+
+ @Test
+ public void testDelete_withNullMethodOutcome_throwsException() {
+ try {
+ myPatient.setId(TEST_PATIENT_ID);
+ myClient.delete().resource(myPatient).execute();
+ fail();
+ } catch (InternalErrorException e){
+ assertTrue(e.getMessage().contains("HTTP 500 Server Error: HAPI-0368"));
+ }
+ }
+
+ public static class NullMethodOutcomePatientProvider implements IResourceProvider {
+
+ @Create
+ public MethodOutcome create(@ResourceParam Patient thePatient) {
+ return null;
+ }
+
+ @Update
+ public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) {
+ return null;
+ }
+
+ @Patch
+ public MethodOutcome patch(@IdParam IdType theId, @ResourceParam String theBody, PatchTypeEnum thePatchType){
+ return null;
+ }
+
+ @Delete
+ public MethodOutcome delete(@IdParam IdType theId) {
+ return null;
+ }
+
+ @Validate
+ public MethodOutcome validate(@ResourceParam Patient thePatient) {
+ return null;
+ }
+
+ public Class extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+ }
+}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerConcurrencyTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerConcurrencyTest.java
index 950f625b55a..6c1b9d50600 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerConcurrencyTest.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerConcurrencyTest.java
@@ -9,6 +9,11 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.test.utilities.HttpClientExtension;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import com.helger.commons.collection.iterate.EmptyEnumeration;
+import jakarta.annotation.Nonnull;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.iterators.IteratorEnumeration;
import org.apache.commons.lang3.RandomStringUtils;
import org.hl7.fhir.r4.model.IdType;
@@ -23,11 +28,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
-import jakarta.annotation.Nonnull;
-import jakarta.servlet.ReadListener;
-import jakarta.servlet.ServletInputStream;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
@@ -36,7 +36,9 @@ import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Set;
+import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
@@ -109,6 +111,13 @@ public class ServerConcurrencyTest {
}
return new EmptyEnumeration<>();
});
+ when(myRequest.getHeaderNames()).thenAnswer(t -> {
+ Set headerNames = myHeaders.keySet();
+ if (!isEmpty(headerNames)){
+ return new IteratorEnumeration<>(headerNames.iterator());
+ }
+ return new EmptyEnumeration<>();
+ });
}
/**
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/bundle/BundleUtilTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/bundle/BundleUtilTest.java
index 20059362e67..ccbb3c60080 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/bundle/BundleUtilTest.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/bundle/BundleUtilTest.java
@@ -41,6 +41,7 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
import static org.hl7.fhir.r4.model.Bundle.HTTPVerb.DELETE;
import static org.hl7.fhir.r4.model.Bundle.HTTPVerb.GET;
import static org.hl7.fhir.r4.model.Bundle.HTTPVerb.POST;
@@ -464,6 +465,96 @@ public class BundleUtilTest {
assertThat(searchBundleEntryParts.get(1).getFullUrl(), is(containsString("Condition/")));
assertThat(searchBundleEntryParts.get(1).getResource(), is(notNullValue()));
}
+ @Test
+ public void testConvertingToSearchBundleEntryPartsRespectsMissingMode() {
+
+ //Given
+ String bundleString = """
+ {
+ "resourceType": "Bundle",
+ "id": "bd194b7f-ac1e-429a-a206-ee2c470f23b5",
+ "type": "searchset",
+ "total": 1,
+ "link": [
+ {
+ "relation": "self",
+ "url": "http://localhost:8000/Condition?_count=1"
+ }
+ ],
+ "entry": [
+ {
+ "fullUrl": "http://localhost:8000/Condition/1626",
+ "resource": {
+ "resourceType": "Condition",
+ "id": "1626",
+ "identifier": [
+ {
+ "system": "urn:hssc:musc:conditionid",
+ "value": "1064115000.1.5"
+ }
+ ]
+ }
+ }
+ ]
+ }""";
+ Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, bundleString);
+
+ //When
+ List searchBundleEntryParts = BundleUtil.getSearchBundleEntryParts(ourCtx, bundle);
+
+ //Then
+ assertThat(searchBundleEntryParts, hasSize(1));
+ assertThat(searchBundleEntryParts.get(0).getSearchMode(), is(nullValue()));
+ assertThat(searchBundleEntryParts.get(0).getFullUrl(), is(containsString("Condition/1626")));
+ assertThat(searchBundleEntryParts.get(0).getResource(), is(notNullValue()));
+ }
+
+ @Test
+ public void testConvertingToSearchBundleEntryPartsRespectsOutcomeMode() {
+
+ //Given
+ String bundleString = """
+ {
+ "resourceType": "Bundle",
+ "id": "bd194b7f-ac1e-429a-a206-ee2c470f23b5",
+ "type": "searchset",
+ "total": 1,
+ "link": [
+ {
+ "relation": "self",
+ "url": "http://localhost:8000/Condition?_count=1"
+ }
+ ],
+ "entry": [
+ {
+ "fullUrl": "http://localhost:8000/Condition/1626",
+ "resource": {
+ "resourceType": "Condition",
+ "id": "1626",
+ "identifier": [
+ {
+ "system": "urn:hssc:musc:conditionid",
+ "value": "1064115000.1.5"
+ }
+ ]
+ },
+ "search": {
+ "mode": "outcome"
+ }
+ }
+ ]
+ }""";
+ Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, bundleString);
+
+ //When
+ List searchBundleEntryParts = BundleUtil.getSearchBundleEntryParts(ourCtx, bundle);
+
+ //Then
+ assertThat(searchBundleEntryParts, hasSize(1));
+ assertThat(searchBundleEntryParts.get(0).getSearchMode(), is(equalTo(BundleEntrySearchModeEnum.OUTCOME)));
+ assertThat(searchBundleEntryParts.get(0).getFullUrl(), is(containsString("Condition/1626")));
+ assertThat(searchBundleEntryParts.get(0).getResource(), is(notNullValue()));
+ }
@Test
public void testTransactionSorterReturnsDeletesInCorrectProcessingOrder() {
diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml
index fb0fdd98b16..d852ad84473 100644
--- a/hapi-fhir-structures-r4b/pom.xml
+++ b/hapi-fhir-structures-r4b/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml
index 6a59995b63f..1c88c4f4869 100644
--- a/hapi-fhir-structures-r5/pom.xml
+++ b/hapi-fhir-structures-r5/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml
index a167088c490..ff040fa535c 100644
--- a/hapi-fhir-test-utilities/pom.xml
+++ b/hapi-fhir-test-utilities/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml
index 003ebc562d8..0081f27ff59 100644
--- a/hapi-fhir-testpage-overlay/pom.xml
+++ b/hapi-fhir-testpage-overlay/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../pom.xml
diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml
index a590bc42c93..be3911ce0ac 100644
--- a/hapi-fhir-validation-resources-dstu2.1/pom.xml
+++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml
index d01beb852a7..ab34e8d8bcc 100644
--- a/hapi-fhir-validation-resources-dstu2/pom.xml
+++ b/hapi-fhir-validation-resources-dstu2/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml
index 37fe068e49c..522ac2b450c 100644
--- a/hapi-fhir-validation-resources-dstu3/pom.xml
+++ b/hapi-fhir-validation-resources-dstu3/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml
index 9f14436198b..bb7ed613bb1 100644
--- a/hapi-fhir-validation-resources-r4/pom.xml
+++ b/hapi-fhir-validation-resources-r4/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-validation-resources-r4b/pom.xml b/hapi-fhir-validation-resources-r4b/pom.xml
index 1547065821b..fb7965abff1 100644
--- a/hapi-fhir-validation-resources-r4b/pom.xml
+++ b/hapi-fhir-validation-resources-r4b/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml
index 8d3945ee89f..11e616c37cd 100644
--- a/hapi-fhir-validation-resources-r5/pom.xml
+++ b/hapi-fhir-validation-resources-r5/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml
index bef9c61146d..bd6a099c68c 100644
--- a/hapi-fhir-validation/pom.xml
+++ b/hapi-fhir-validation/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
index d85cf5e4ee9..372b8ead4a8 100644
--- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
+++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
@@ -2589,6 +2589,9 @@ public class AuthorizationInterceptorR4Test extends BaseValidationTestWithInline
assertEquals(false, ourHitMethod);
}
+ // This test is of dubious value since it does NOT exercise DAO code. It simply exercises the AuthorizationInterceptor.
+ // In functional testing or with a more realistic integration test, this scenario, namely having ONLY a FHIR_PATCH
+ // role, will result in a failure to update the resource.
@Test
public void testPatchAllowed() throws IOException {
Observation obs = new Observation();
diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml
index 4bfad09b5f6..4fb7cf0d88e 100644
--- a/hapi-tinder-plugin/pom.xml
+++ b/hapi-tinder-plugin/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../pom.xml
diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml
index 483bd1ca8d7..5f84df6f169 100644
--- a/hapi-tinder-test/pom.xml
+++ b/hapi-tinder-test/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../pom.xml
diff --git a/pom.xml b/pom.xml
index 7a01fefadce..a3004385ff1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
ca.uhn.hapi.fhir
hapi-fhir
pom
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
HAPI-FHIR
An open-source implementation of the FHIR specification in Java.
@@ -989,7 +989,7 @@
2.2.19
2.0.9
2.19.0
- 6.1.1
+ 6.1.5
2023.1.0
4.3.3
3.2.0
@@ -1006,7 +1006,7 @@
1.0.8
- 3.0.0-PRE17
+ 3.2.0
5.4.1
@@ -1990,7 +1990,7 @@
org.postgresql
postgresql
- 42.7.1
+ 42.7.3
com.oracle.database.jdbc
diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml
index 3b0c9d0200d..5f119fcd90b 100644
--- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml
+++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../../pom.xml
diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml
index 245d4adf698..4250751cced 100644
--- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml
+++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../../pom.xml
diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml
index 8842b7c244f..0fd0f7c3774 100644
--- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml
+++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 7.1.6-SNAPSHOT
+ 7.1.7-SNAPSHOT
../../pom.xml