6530 refactor export operation (#6531)
extract reusable methods out of bulk export provider --------- Co-authored-by: Vadim Karantayer <vadim.karantayer@smilecdr.com>
This commit is contained in:
parent
b904aac3c9
commit
2d740f8d85
|
@ -21,11 +21,15 @@ package ca.uhn.fhir.util;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class DatatypeUtil {
|
public class DatatypeUtil {
|
||||||
|
private DatatypeUtil() {
|
||||||
|
// non-instantiable
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a list of FHIR String objects to a set of native java Strings
|
* Convert a list of FHIR String objects to a set of native java Strings
|
||||||
|
@ -74,4 +78,12 @@ public class DatatypeUtil {
|
||||||
public static Boolean toBooleanValue(IPrimitiveType<Boolean> thePrimitiveType) {
|
public static Boolean toBooleanValue(IPrimitiveType<Boolean> thePrimitiveType) {
|
||||||
return thePrimitiveType != null ? thePrimitiveType.getValue() : null;
|
return thePrimitiveType != null ? thePrimitiveType.getValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@link IPrimitiveType#getValue()} if <code>thePrimitiveType</code> is
|
||||||
|
* not null, else returns null.
|
||||||
|
*/
|
||||||
|
public static Date toDateValue(IPrimitiveType<Date> thePrimitiveType) {
|
||||||
|
return thePrimitiveType != null ? thePrimitiveType.getValue() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -442,12 +442,12 @@ public class JpaBulkExportProcessor implements IBulkExportProcessor<JpaPid> {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private List<JpaPid> getMembersFromGroupWithFilter(
|
private List<JpaPid> getMembersFromGroupWithFilter(
|
||||||
ExportPIDIteratorParameters theParameters, boolean theConsiderSince) throws IOException {
|
ExportPIDIteratorParameters theParameters, boolean theConsiderDateRange) throws IOException {
|
||||||
RuntimeResourceDefinition def = myContext.getResourceDefinition("Patient");
|
RuntimeResourceDefinition def = myContext.getResourceDefinition("Patient");
|
||||||
List<JpaPid> resPids = new ArrayList<>();
|
List<JpaPid> resPids = new ArrayList<>();
|
||||||
|
|
||||||
List<SearchParameterMap> maps =
|
List<SearchParameterMap> maps = myBulkExportHelperSvc.createSearchParameterMapsForResourceType(
|
||||||
myBulkExportHelperSvc.createSearchParameterMapsForResourceType(def, theParameters, theConsiderSince);
|
def, theParameters, theConsiderDateRange);
|
||||||
|
|
||||||
maps.forEach(map -> addMembershipToGroupClause(map, theParameters.getGroupId()));
|
maps.forEach(map -> addMembershipToGroupClause(map, theParameters.getGroupId()));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
package ca.uhn.fhir.jpa.bulk.export.svc;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
|
import ca.uhn.fhir.util.DateUtils;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class BulkExportHelperServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private MatchUrlService myMatchUrlService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FhirContext myContext;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BulkExportHelperService myFixture;
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
verifyNoMoreInteractions(myMatchUrlService, myContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addLastUpdatedFilterShouldBeNullWhenNoDatesGiven() {
|
||||||
|
// Arrange
|
||||||
|
final SearchParameterMap searchParameterMap = new SearchParameterMap();
|
||||||
|
// Act
|
||||||
|
myFixture.addLastUpdatedFilter(searchParameterMap, null, null);
|
||||||
|
// Assert
|
||||||
|
assertThat(searchParameterMap.getLastUpdated()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addLastUpdatedFilterShouldContainsStartDateWhenStartDateIsGiven() {
|
||||||
|
// Arrange
|
||||||
|
final SearchParameterMap searchParameterMap = new SearchParameterMap();
|
||||||
|
final Date startDate = new Date();
|
||||||
|
final DateRangeParam expected = new DateRangeParam(startDate, null);
|
||||||
|
// Act
|
||||||
|
myFixture.addLastUpdatedFilter(searchParameterMap, startDate, null);
|
||||||
|
// Assert
|
||||||
|
assertThat(searchParameterMap.getLastUpdated()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addLastUpdatedFilterShouldContainsEndDateWhenEndDateIsGiven() {
|
||||||
|
// Arrange
|
||||||
|
final SearchParameterMap searchParameterMap = new SearchParameterMap();
|
||||||
|
final Date endDate = new Date();
|
||||||
|
final DateRangeParam expected = new DateRangeParam(null, endDate);
|
||||||
|
// Act
|
||||||
|
myFixture.addLastUpdatedFilter(searchParameterMap, null, endDate);
|
||||||
|
// Assert
|
||||||
|
assertThat(searchParameterMap.getLastUpdated()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addLastUpdatedFilterShouldContainsDateRangeWhenStartAndEndDateIsGiven() {
|
||||||
|
// Arrange
|
||||||
|
final SearchParameterMap searchParameterMap = new SearchParameterMap();
|
||||||
|
final Date startDate = new Date();
|
||||||
|
final Date endDate = DateUtils.getEndOfDay(startDate);
|
||||||
|
final DateRangeParam expected = new DateRangeParam(startDate, endDate);
|
||||||
|
// Act
|
||||||
|
myFixture.addLastUpdatedFilter(searchParameterMap, startDate, endDate);
|
||||||
|
// Assert
|
||||||
|
assertThat(searchParameterMap.getLastUpdated()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -41,14 +41,21 @@ public class BulkExportJobParameters extends BaseBatchJobParameters {
|
||||||
private List<String> myResourceTypes;
|
private List<String> myResourceTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The start date from when we should start
|
* The start date from when we should start doing the export.
|
||||||
* doing the export. (end date is assumed to be "now")
|
|
||||||
*/
|
*/
|
||||||
@JsonSerialize(using = JsonDateSerializer.class)
|
@JsonSerialize(using = JsonDateSerializer.class)
|
||||||
@JsonDeserialize(using = JsonDateDeserializer.class)
|
@JsonDeserialize(using = JsonDateDeserializer.class)
|
||||||
@JsonProperty("since")
|
@JsonProperty("since")
|
||||||
private Date mySince;
|
private Date mySince;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end date to which we should stop doing the export.
|
||||||
|
*/
|
||||||
|
@JsonSerialize(using = JsonDateSerializer.class)
|
||||||
|
@JsonDeserialize(using = JsonDateDeserializer.class)
|
||||||
|
@JsonProperty("until")
|
||||||
|
private Date myUntil;
|
||||||
|
|
||||||
@JsonProperty("exportId")
|
@JsonProperty("exportId")
|
||||||
private String myExportId;
|
private String myExportId;
|
||||||
|
|
||||||
|
@ -147,6 +154,14 @@ public class BulkExportJobParameters extends BaseBatchJobParameters {
|
||||||
mySince = theSince;
|
mySince = theSince;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Date getUntil() {
|
||||||
|
return myUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUntil(Date theUntil) {
|
||||||
|
myUntil = theUntil;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getFilters() {
|
public List<String> getFilters() {
|
||||||
if (myFilters == null) {
|
if (myFilters == null) {
|
||||||
myFilters = new ArrayList<>();
|
myFilters = new ArrayList<>();
|
||||||
|
|
|
@ -22,19 +22,14 @@ package ca.uhn.fhir.batch2.jobs.export;
|
||||||
import ca.uhn.fhir.batch2.api.IJobCoordinator;
|
import ca.uhn.fhir.batch2.api.IJobCoordinator;
|
||||||
import ca.uhn.fhir.batch2.api.JobOperationResultJson;
|
import ca.uhn.fhir.batch2.api.JobOperationResultJson;
|
||||||
import ca.uhn.fhir.batch2.model.JobInstance;
|
import ca.uhn.fhir.batch2.model.JobInstance;
|
||||||
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
|
|
||||||
import ca.uhn.fhir.batch2.model.StatusEnum;
|
import ca.uhn.fhir.batch2.model.StatusEnum;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.model.BulkExportJobResults;
|
import ca.uhn.fhir.jpa.api.model.BulkExportJobResults;
|
||||||
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
|
|
||||||
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportResponseJson;
|
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportResponseJson;
|
||||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
|
@ -42,28 +37,16 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.PreferHeader;
|
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|
||||||
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
|
||||||
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
|
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
|
||||||
import ca.uhn.fhir.util.ArrayUtil;
|
|
||||||
import ca.uhn.fhir.util.Batch2JobDefinitionConstants;
|
|
||||||
import ca.uhn.fhir.util.JsonUtil;
|
import ca.uhn.fhir.util.JsonUtil;
|
||||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||||
import ca.uhn.fhir.util.SearchParameterUtil;
|
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
@ -75,44 +58,23 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters.ExportStyle;
|
import static ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters.ExportStyle;
|
||||||
import static ca.uhn.fhir.util.DatatypeUtil.toStringValue;
|
|
||||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isEmpty;
|
import static org.apache.commons.lang3.StringUtils.isEmpty;
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
import static org.slf4j.LoggerFactory.getLogger;
|
||||||
|
|
||||||
public class BulkDataExportProvider {
|
public class BulkDataExportProvider {
|
||||||
public static final String FARM_TO_TABLE_TYPE_FILTER_REGEX = "(?:,)(?=[A-Z][a-z]+\\?)";
|
|
||||||
public static final List<String> PATIENT_BULK_EXPORT_FORWARD_REFERENCE_RESOURCE_TYPES =
|
|
||||||
List.of("Practitioner", "Organization");
|
|
||||||
/**
|
|
||||||
* Bulk data $export does not include the Binary type
|
|
||||||
*/
|
|
||||||
public static final String UNSUPPORTED_BINARY_TYPE = "Binary";
|
|
||||||
|
|
||||||
private static final Logger ourLog = getLogger(BulkDataExportProvider.class);
|
private static final Logger ourLog = getLogger(BulkDataExportProvider.class);
|
||||||
private static final Set<FhirVersionEnum> PATIENT_COMPARTMENT_FHIR_VERSIONS_SUPPORT_DEVICE = Set.of(
|
|
||||||
FhirVersionEnum.DSTU2,
|
|
||||||
FhirVersionEnum.DSTU2_1,
|
|
||||||
FhirVersionEnum.DSTU2_HL7ORG,
|
|
||||||
FhirVersionEnum.DSTU3,
|
|
||||||
FhirVersionEnum.R4,
|
|
||||||
FhirVersionEnum.R4B);
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
|
||||||
private Set<String> myCompartmentResources;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
|
|
||||||
|
@ -167,85 +129,19 @@ public class BulkDataExportProvider {
|
||||||
IPrimitiveType<String> theExportId,
|
IPrimitiveType<String> theExportId,
|
||||||
ServletRequestDetails theRequestDetails) {
|
ServletRequestDetails theRequestDetails) {
|
||||||
// JPA export provider
|
// JPA export provider
|
||||||
validatePreferAsyncHeader(theRequestDetails, ProviderConstants.OPERATION_EXPORT);
|
BulkDataExportUtil.validatePreferAsyncHeader(theRequestDetails, ProviderConstants.OPERATION_EXPORT);
|
||||||
|
|
||||||
BulkExportJobParameters BulkExportJobParameters = buildSystemBulkExportOptions(
|
BulkExportJobParameters bulkExportJobParameters = new BulkExportJobParametersBuilder()
|
||||||
theOutputFormat, theType, theSince, theTypeFilter, theExportId, theTypePostFetchFilterUrl);
|
.outputFormat(theOutputFormat)
|
||||||
|
.resourceTypes(theType)
|
||||||
|
.since(theSince)
|
||||||
|
.filters(theTypeFilter)
|
||||||
|
.exportIdentifier(theExportId)
|
||||||
|
.exportStyle(ExportStyle.SYSTEM)
|
||||||
|
.postFetchFilterUrl(theTypePostFetchFilterUrl)
|
||||||
|
.build();
|
||||||
|
|
||||||
startJob(theRequestDetails, BulkExportJobParameters);
|
getBulkDataExportJobService().startJob(theRequestDetails, bulkExportJobParameters);
|
||||||
}
|
|
||||||
|
|
||||||
private void startJob(ServletRequestDetails theRequestDetails, BulkExportJobParameters theOptions) {
|
|
||||||
// parameter massaging
|
|
||||||
expandParameters(theRequestDetails, theOptions);
|
|
||||||
|
|
||||||
// permission check
|
|
||||||
IInterceptorBroadcaster compositeBroadcaster =
|
|
||||||
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequestDetails);
|
|
||||||
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_INITIATE_BULK_EXPORT)) {
|
|
||||||
HookParams initiateBulkExportHookParams = (new HookParams())
|
|
||||||
.add(BulkExportJobParameters.class, theOptions)
|
|
||||||
.add(RequestDetails.class, theRequestDetails)
|
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
|
|
||||||
compositeBroadcaster.callHooks(Pointcut.STORAGE_INITIATE_BULK_EXPORT, initiateBulkExportHookParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get cache boolean
|
|
||||||
boolean useCache = shouldUseCache(theRequestDetails);
|
|
||||||
|
|
||||||
// start job
|
|
||||||
JobInstanceStartRequest startRequest = new JobInstanceStartRequest();
|
|
||||||
startRequest.setParameters(theOptions);
|
|
||||||
startRequest.setUseCache(useCache);
|
|
||||||
startRequest.setJobDefinitionId(Batch2JobDefinitionConstants.BULK_EXPORT);
|
|
||||||
Batch2JobStartResponse response = myJobCoordinator.startInstance(theRequestDetails, startRequest);
|
|
||||||
|
|
||||||
writePollingLocationToResponseHeaders(theRequestDetails, response.getInstanceId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method changes any parameters (limiting the _type parameter, for instance)
|
|
||||||
* so that later steps in the export do not have to handle them.
|
|
||||||
*/
|
|
||||||
private void expandParameters(ServletRequestDetails theRequestDetails, BulkExportJobParameters theOptions) {
|
|
||||||
// Set the original request URL as part of the job information, as this is used in the poll-status-endpoint, and
|
|
||||||
// is needed for the report.
|
|
||||||
theOptions.setOriginalRequestUrl(theRequestDetails.getCompleteUrl());
|
|
||||||
|
|
||||||
// If no _type parameter is provided, default to all resource types except Binary
|
|
||||||
if (theOptions.getResourceTypes().isEmpty()) {
|
|
||||||
List<String> resourceTypes = new ArrayList<>(myDaoRegistry.getRegisteredDaoTypes());
|
|
||||||
resourceTypes.remove(UNSUPPORTED_BINARY_TYPE);
|
|
||||||
theOptions.setResourceTypes(resourceTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine and validate partition permissions (if needed).
|
|
||||||
RequestPartitionId partitionId =
|
|
||||||
myRequestPartitionHelperService.determineReadPartitionForRequestForServerOperation(
|
|
||||||
theRequestDetails, ProviderConstants.OPERATION_EXPORT);
|
|
||||||
myRequestPartitionHelperService.validateHasPartitionPermissions(theRequestDetails, "Binary", partitionId);
|
|
||||||
theOptions.setPartitionId(partitionId);
|
|
||||||
|
|
||||||
// call hook so any other parameter manipulation can be done
|
|
||||||
IInterceptorBroadcaster compositeBroadcaster =
|
|
||||||
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequestDetails);
|
|
||||||
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PRE_INITIATE_BULK_EXPORT)) {
|
|
||||||
HookParams preInitiateBulkExportHookParams = new HookParams();
|
|
||||||
preInitiateBulkExportHookParams.add(BulkExportJobParameters.class, theOptions);
|
|
||||||
preInitiateBulkExportHookParams.add(RequestDetails.class, theRequestDetails);
|
|
||||||
preInitiateBulkExportHookParams.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
|
|
||||||
compositeBroadcaster.callHooks(Pointcut.STORAGE_PRE_INITIATE_BULK_EXPORT, preInitiateBulkExportHookParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldUseCache(ServletRequestDetails theRequestDetails) {
|
|
||||||
CacheControlDirective cacheControlDirective =
|
|
||||||
new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL));
|
|
||||||
return myStorageSettings.getEnableBulkExportJobReuse() && !cacheControlDirective.isNoCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getServerBase(ServletRequestDetails theRequestDetails) {
|
|
||||||
return StringUtils.removeEnd(theRequestDetails.getServerBaseForRequest(), "/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,87 +184,30 @@ public class BulkDataExportProvider {
|
||||||
ourLog.debug("_typeFilter={}", theTypeFilter);
|
ourLog.debug("_typeFilter={}", theTypeFilter);
|
||||||
ourLog.debug("_mdm={}", theMdm);
|
ourLog.debug("_mdm={}", theMdm);
|
||||||
|
|
||||||
validatePreferAsyncHeader(theRequestDetails, ProviderConstants.OPERATION_EXPORT);
|
BulkDataExportUtil.validatePreferAsyncHeader(theRequestDetails, ProviderConstants.OPERATION_EXPORT);
|
||||||
|
|
||||||
// verify the Group exists before starting the job
|
// verify the Group exists before starting the job
|
||||||
validateTargetsExists(theRequestDetails, "Group", List.of(theIdParam));
|
getBulkDataExportSupport().validateTargetsExists(theRequestDetails, "Group", List.of(theIdParam));
|
||||||
|
|
||||||
BulkExportJobParameters BulkExportJobParameters = buildGroupBulkExportOptions(
|
final BulkExportJobParameters bulkExportJobParameters = new BulkExportJobParametersBuilder()
|
||||||
theOutputFormat,
|
.outputFormat(theOutputFormat)
|
||||||
theType,
|
.resourceTypes(theType)
|
||||||
theSince,
|
.since(theSince)
|
||||||
theTypeFilter,
|
.filters(theTypeFilter)
|
||||||
theIdParam,
|
.exportIdentifier(theExportIdentifier)
|
||||||
theMdm,
|
.exportStyle(ExportStyle.GROUP)
|
||||||
theExportIdentifier,
|
.postFetchFilterUrl(theTypePostFetchFilterUrl)
|
||||||
theTypePostFetchFilterUrl);
|
.groupId(theIdParam)
|
||||||
|
.expandMdm(theMdm)
|
||||||
|
.build();
|
||||||
|
|
||||||
if (isNotEmpty(BulkExportJobParameters.getResourceTypes())) {
|
getBulkDataExportSupport().validateOrDefaultResourceTypesForGroupBulkExport(bulkExportJobParameters);
|
||||||
validateResourceTypesAllContainPatientSearchParams(BulkExportJobParameters.getResourceTypes());
|
getBulkDataExportJobService().startJob(theRequestDetails, bulkExportJobParameters);
|
||||||
} else {
|
|
||||||
// all patient resource types
|
|
||||||
Set<String> groupTypes = new HashSet<>(getPatientCompartmentResources());
|
|
||||||
|
|
||||||
// Add the forward reference resource types from the patients, e.g. Practitioner, Organization
|
|
||||||
groupTypes.addAll(PATIENT_BULK_EXPORT_FORWARD_REFERENCE_RESOURCE_TYPES);
|
|
||||||
|
|
||||||
groupTypes.removeIf(t -> !myDaoRegistry.isResourceTypeSupported(t));
|
|
||||||
BulkExportJobParameters.setResourceTypes(groupTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
startJob(theRequestDetails, BulkExportJobParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throw ResourceNotFound if the target resources don't exist.
|
|
||||||
* Otherwise, we start a bulk-export job which then fails, reporting a 500.
|
|
||||||
*
|
|
||||||
* @param theRequestDetails the caller details
|
|
||||||
* @param theTargetResourceName the type of the target
|
|
||||||
* @param theIdParams the id(s) to verify exist
|
|
||||||
*/
|
|
||||||
private void validateTargetsExists(
|
|
||||||
RequestDetails theRequestDetails, String theTargetResourceName, Iterable<IIdType> theIdParams) {
|
|
||||||
if (theIdParams.iterator().hasNext()) {
|
|
||||||
RequestPartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForRead(
|
|
||||||
theRequestDetails,
|
|
||||||
theTargetResourceName,
|
|
||||||
theIdParams.iterator().next());
|
|
||||||
SystemRequestDetails requestDetails = new SystemRequestDetails().setRequestPartitionId(partitionId);
|
|
||||||
for (IIdType nextId : theIdParams) {
|
|
||||||
myDaoRegistry.getResourceDao(theTargetResourceName).read(nextId, requestDetails);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateResourceTypesAllContainPatientSearchParams(Collection<String> theResourceTypes) {
|
|
||||||
if (theResourceTypes != null) {
|
|
||||||
List<String> badResourceTypes = theResourceTypes.stream()
|
|
||||||
.filter(resourceType ->
|
|
||||||
!PATIENT_BULK_EXPORT_FORWARD_REFERENCE_RESOURCE_TYPES.contains(resourceType))
|
|
||||||
.filter(resourceType -> !getPatientCompartmentResources().contains(resourceType))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (!badResourceTypes.isEmpty()) {
|
|
||||||
throw new InvalidRequestException(Msg.code(512)
|
|
||||||
+ String.format(
|
|
||||||
"Resource types [%s] are invalid for this type of export, as they do not contain search parameters that refer to patients.",
|
|
||||||
String.join(",", badResourceTypes)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getPatientCompartmentResources() {
|
|
||||||
return getPatientCompartmentResources(myFhirContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
Set<String> getPatientCompartmentResources(FhirContext theFhirContext) {
|
Set<String> getPatientCompartmentResources(FhirContext theFhirContext) {
|
||||||
if (myCompartmentResources == null) {
|
return getBulkDataExportSupport().getPatientCompartmentResources(theFhirContext);
|
||||||
myCompartmentResources =
|
|
||||||
new HashSet<>(SearchParameterUtil.getAllResourceTypesThatAreInPatientCompartment(theFhirContext));
|
|
||||||
}
|
|
||||||
return myCompartmentResources;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -475,24 +314,36 @@ public class BulkDataExportProvider {
|
||||||
List<IPrimitiveType<String>> theTypeFilter,
|
List<IPrimitiveType<String>> theTypeFilter,
|
||||||
List<IPrimitiveType<String>> theTypePostFetchFilterUrl,
|
List<IPrimitiveType<String>> theTypePostFetchFilterUrl,
|
||||||
List<IPrimitiveType<String>> thePatientIds) {
|
List<IPrimitiveType<String>> thePatientIds) {
|
||||||
validatePreferAsyncHeader(theRequestDetails, ProviderConstants.OPERATION_EXPORT);
|
BulkDataExportUtil.validatePreferAsyncHeader(theRequestDetails, ProviderConstants.OPERATION_EXPORT);
|
||||||
|
|
||||||
validateTargetsExists(
|
getBulkDataExportSupport()
|
||||||
theRequestDetails,
|
.validateTargetsExists(
|
||||||
"Patient",
|
theRequestDetails,
|
||||||
thePatientIds.stream().map(c -> new IdType(c.getValue())).collect(Collectors.toList()));
|
"Patient",
|
||||||
|
thePatientIds.stream()
|
||||||
|
.map(c -> new IdType(c.getValue()))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
BulkExportJobParameters BulkExportJobParameters = buildPatientBulkExportOptions(
|
// set resourceTypes to all patient compartment resources if it is null
|
||||||
theOutputFormat,
|
IPrimitiveType<String> resourceTypes = theType == null
|
||||||
theType,
|
? new StringDt(String.join(",", getBulkDataExportSupport().getPatientCompartmentResources()))
|
||||||
theSince,
|
: theType;
|
||||||
theTypeFilter,
|
|
||||||
theExportIdentifier,
|
|
||||||
thePatientIds,
|
|
||||||
theTypePostFetchFilterUrl);
|
|
||||||
validateResourceTypesAllContainPatientSearchParams(BulkExportJobParameters.getResourceTypes());
|
|
||||||
|
|
||||||
startJob(theRequestDetails, BulkExportJobParameters);
|
BulkExportJobParameters bulkExportJobParameters = new BulkExportJobParametersBuilder()
|
||||||
|
.outputFormat(theOutputFormat)
|
||||||
|
.resourceTypes(resourceTypes)
|
||||||
|
.since(theSince)
|
||||||
|
.filters(theTypeFilter)
|
||||||
|
.exportIdentifier(theExportIdentifier)
|
||||||
|
.exportStyle(ExportStyle.PATIENT)
|
||||||
|
.postFetchFilterUrl(theTypePostFetchFilterUrl)
|
||||||
|
.patientIds(thePatientIds)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
getBulkDataExportSupport()
|
||||||
|
.validateResourceTypesAllContainPatientSearchParams(bulkExportJobParameters.getResourceTypes());
|
||||||
|
|
||||||
|
getBulkDataExportJobService().startJob(theRequestDetails, bulkExportJobParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -563,7 +414,7 @@ public class BulkDataExportProvider {
|
||||||
bulkResponseDocument.setMsg(results.getReportMsg());
|
bulkResponseDocument.setMsg(results.getReportMsg());
|
||||||
bulkResponseDocument.setRequest(results.getOriginalRequestUrl());
|
bulkResponseDocument.setRequest(results.getOriginalRequestUrl());
|
||||||
|
|
||||||
String serverBase = getServerBase(theRequestDetails);
|
String serverBase = BulkDataExportUtil.getServerBase(theRequestDetails);
|
||||||
|
|
||||||
// an output is required, even if empty, according to HL7 FHIR IG
|
// an output is required, even if empty, according to HL7 FHIR IG
|
||||||
bulkResponseDocument.getOutput();
|
bulkResponseDocument.getOutput();
|
||||||
|
@ -658,153 +509,6 @@ public class BulkDataExportProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BulkExportJobParameters buildSystemBulkExportOptions(
|
|
||||||
IPrimitiveType<String> theOutputFormat,
|
|
||||||
IPrimitiveType<String> theType,
|
|
||||||
IPrimitiveType<Date> theSince,
|
|
||||||
List<IPrimitiveType<String>> theTypeFilter,
|
|
||||||
IPrimitiveType<String> theExportId,
|
|
||||||
List<IPrimitiveType<String>> theTypePostFetchFilterUrl) {
|
|
||||||
return buildBulkExportJobParameters(
|
|
||||||
theOutputFormat,
|
|
||||||
theType,
|
|
||||||
theSince,
|
|
||||||
theTypeFilter,
|
|
||||||
theExportId,
|
|
||||||
BulkExportJobParameters.ExportStyle.SYSTEM,
|
|
||||||
theTypePostFetchFilterUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BulkExportJobParameters buildGroupBulkExportOptions(
|
|
||||||
IPrimitiveType<String> theOutputFormat,
|
|
||||||
IPrimitiveType<String> theType,
|
|
||||||
IPrimitiveType<Date> theSince,
|
|
||||||
List<IPrimitiveType<String>> theTypeFilter,
|
|
||||||
IIdType theGroupId,
|
|
||||||
IPrimitiveType<Boolean> theExpandMdm,
|
|
||||||
IPrimitiveType<String> theExportId,
|
|
||||||
List<IPrimitiveType<String>> theTypePostFetchFilterUrl) {
|
|
||||||
BulkExportJobParameters BulkExportJobParameters = buildBulkExportJobParameters(
|
|
||||||
theOutputFormat,
|
|
||||||
theType,
|
|
||||||
theSince,
|
|
||||||
theTypeFilter,
|
|
||||||
theExportId,
|
|
||||||
ExportStyle.GROUP,
|
|
||||||
theTypePostFetchFilterUrl);
|
|
||||||
BulkExportJobParameters.setGroupId(toStringValue(theGroupId));
|
|
||||||
|
|
||||||
boolean mdm = false;
|
|
||||||
if (theExpandMdm != null) {
|
|
||||||
mdm = theExpandMdm.getValue();
|
|
||||||
}
|
|
||||||
BulkExportJobParameters.setExpandMdm(mdm);
|
|
||||||
|
|
||||||
return BulkExportJobParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BulkExportJobParameters buildPatientBulkExportOptions(
|
|
||||||
IPrimitiveType<String> theOutputFormat,
|
|
||||||
IPrimitiveType<String> theType,
|
|
||||||
IPrimitiveType<Date> theSince,
|
|
||||||
List<IPrimitiveType<String>> theTypeFilter,
|
|
||||||
IPrimitiveType<String> theExportIdentifier,
|
|
||||||
List<IPrimitiveType<String>> thePatientIds,
|
|
||||||
List<IPrimitiveType<String>> theTypePostFetchFilterUrl) {
|
|
||||||
IPrimitiveType<String> type = theType;
|
|
||||||
if (type == null) {
|
|
||||||
// set type to all patient compartment resources if it is null
|
|
||||||
type = new StringDt(String.join(",", getPatientCompartmentResources()));
|
|
||||||
}
|
|
||||||
BulkExportJobParameters BulkExportJobParameters = buildBulkExportJobParameters(
|
|
||||||
theOutputFormat,
|
|
||||||
type,
|
|
||||||
theSince,
|
|
||||||
theTypeFilter,
|
|
||||||
theExportIdentifier,
|
|
||||||
ExportStyle.PATIENT,
|
|
||||||
theTypePostFetchFilterUrl);
|
|
||||||
if (thePatientIds != null) {
|
|
||||||
BulkExportJobParameters.setPatientIds(
|
|
||||||
thePatientIds.stream().map(IPrimitiveType::getValueAsString).collect(Collectors.toSet()));
|
|
||||||
}
|
|
||||||
return BulkExportJobParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BulkExportJobParameters buildBulkExportJobParameters(
|
|
||||||
IPrimitiveType<String> theOutputFormat,
|
|
||||||
IPrimitiveType<String> theType,
|
|
||||||
IPrimitiveType<Date> theSince,
|
|
||||||
List<IPrimitiveType<String>> theTypeFilter,
|
|
||||||
IPrimitiveType<String> theExportIdentifier,
|
|
||||||
BulkExportJobParameters.ExportStyle theExportStyle,
|
|
||||||
List<IPrimitiveType<String>> theTypePostFetchFilterUrl) {
|
|
||||||
String outputFormat = theOutputFormat != null ? theOutputFormat.getValueAsString() : Constants.CT_FHIR_NDJSON;
|
|
||||||
|
|
||||||
Set<String> resourceTypes = null;
|
|
||||||
if (theType != null) {
|
|
||||||
resourceTypes = ArrayUtil.commaSeparatedListToCleanSet(theType.getValueAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
Date since = null;
|
|
||||||
if (theSince != null) {
|
|
||||||
since = theSince.getValue();
|
|
||||||
}
|
|
||||||
String exportIdentifier = null;
|
|
||||||
if (theExportIdentifier != null) {
|
|
||||||
exportIdentifier = theExportIdentifier.getValueAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> typeFilters = splitTypeFilters(theTypeFilter);
|
|
||||||
Set<String> typePostFetchFilterUrls = splitTypeFilters(theTypePostFetchFilterUrl);
|
|
||||||
|
|
||||||
BulkExportJobParameters BulkExportJobParameters = new BulkExportJobParameters();
|
|
||||||
BulkExportJobParameters.setFilters(typeFilters);
|
|
||||||
BulkExportJobParameters.setPostFetchFilterUrls(typePostFetchFilterUrls);
|
|
||||||
BulkExportJobParameters.setExportStyle(theExportStyle);
|
|
||||||
BulkExportJobParameters.setExportIdentifier(exportIdentifier);
|
|
||||||
BulkExportJobParameters.setSince(since);
|
|
||||||
BulkExportJobParameters.setResourceTypes(resourceTypes);
|
|
||||||
BulkExportJobParameters.setOutputFormat(outputFormat);
|
|
||||||
return BulkExportJobParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writePollingLocationToResponseHeaders(ServletRequestDetails theRequestDetails, String theInstanceId) {
|
|
||||||
String serverBase = getServerBase(theRequestDetails);
|
|
||||||
if (serverBase == null) {
|
|
||||||
throw new InternalErrorException(Msg.code(2136) + "Unable to get the server base.");
|
|
||||||
}
|
|
||||||
String pollLocation = serverBase + "/" + ProviderConstants.OPERATION_EXPORT_POLL_STATUS + "?"
|
|
||||||
+ JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + theInstanceId;
|
|
||||||
pollLocation = UrlUtil.sanitizeHeaderValue(pollLocation);
|
|
||||||
|
|
||||||
HttpServletResponse response = theRequestDetails.getServletResponse();
|
|
||||||
|
|
||||||
// Add standard headers
|
|
||||||
theRequestDetails.getServer().addHeadersToResponse(response);
|
|
||||||
|
|
||||||
// Successful 202 Accepted
|
|
||||||
response.addHeader(Constants.HEADER_CONTENT_LOCATION, pollLocation);
|
|
||||||
response.setStatus(Constants.STATUS_HTTP_202_ACCEPTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> splitTypeFilters(List<IPrimitiveType<String>> theTypeFilter) {
|
|
||||||
if (theTypeFilter == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> retVal = new HashSet<>();
|
|
||||||
|
|
||||||
for (IPrimitiveType<String> next : theTypeFilter) {
|
|
||||||
String typeFilterString = next.getValueAsString();
|
|
||||||
Arrays.stream(typeFilterString.split(FARM_TO_TABLE_TYPE_FILTER_REGEX))
|
|
||||||
.filter(StringUtils::isNotBlank)
|
|
||||||
.forEach(retVal::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setStorageSettings(JpaStorageSettings theStorageSettings) {
|
public void setStorageSettings(JpaStorageSettings theStorageSettings) {
|
||||||
myStorageSettings = theStorageSettings;
|
myStorageSettings = theStorageSettings;
|
||||||
|
@ -815,11 +519,29 @@ public class BulkDataExportProvider {
|
||||||
myDaoRegistry = theDaoRegistry;
|
myDaoRegistry = theDaoRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void validatePreferAsyncHeader(ServletRequestDetails theRequestDetails, String theOperationName) {
|
// Do not use this variable directly, use getBulkDataExportJobService() instead
|
||||||
String preferHeader = theRequestDetails.getHeader(Constants.HEADER_PREFER);
|
private BulkExportJobService myBulkExportJobService;
|
||||||
PreferHeader prefer = RestfulServerUtils.parsePreferHeader(null, preferHeader);
|
|
||||||
if (!prefer.getRespondAsync()) {
|
private BulkExportJobService getBulkDataExportJobService() {
|
||||||
throw new InvalidRequestException(Msg.code(513) + "Must request async processing for " + theOperationName);
|
if (myBulkExportJobService == null) {
|
||||||
|
myBulkExportJobService = new BulkExportJobService(
|
||||||
|
myInterceptorBroadcaster,
|
||||||
|
myJobCoordinator,
|
||||||
|
myDaoRegistry,
|
||||||
|
myRequestPartitionHelperService,
|
||||||
|
myStorageSettings);
|
||||||
}
|
}
|
||||||
|
return myBulkExportJobService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not use this variable directly, use getBulkDataExportSupport() instead
|
||||||
|
private BulkDataExportSupport myBulkDataExportSupport;
|
||||||
|
|
||||||
|
private BulkDataExportSupport getBulkDataExportSupport() {
|
||||||
|
if (myBulkDataExportSupport == null) {
|
||||||
|
myBulkDataExportSupport =
|
||||||
|
new BulkDataExportSupport(myFhirContext, myDaoRegistry, myRequestPartitionHelperService);
|
||||||
|
}
|
||||||
|
return myBulkDataExportSupport;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* hapi-fhir-storage-batch2-jobs
|
||||||
|
* %%
|
||||||
|
* 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.batch2.jobs.export;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.util.SearchParameterUtil;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for validating if a target exists
|
||||||
|
* and if the requested export resource types contain search references to Patient.
|
||||||
|
* In addition to that it also sets resource types in BulkExportJobParameters,
|
||||||
|
* which are part of Patient compartment, when export resource types are not provided.
|
||||||
|
*/
|
||||||
|
public class BulkDataExportSupport {
|
||||||
|
private final FhirContext myFhirContext;
|
||||||
|
private final DaoRegistry myDaoRegistry;
|
||||||
|
private final IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||||
|
private Set<String> myCompartmentResources;
|
||||||
|
|
||||||
|
public BulkDataExportSupport(
|
||||||
|
@Nonnull FhirContext theFhirContext,
|
||||||
|
@Nonnull DaoRegistry theDaoRegistry,
|
||||||
|
@Nonnull IRequestPartitionHelperSvc theRequestPartitionHelperService) {
|
||||||
|
myFhirContext = theFhirContext;
|
||||||
|
myDaoRegistry = theDaoRegistry;
|
||||||
|
myRequestPartitionHelperService = theRequestPartitionHelperService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw ResourceNotFound if the target resources don't exist.
|
||||||
|
*
|
||||||
|
* @param theRequestDetails the caller details
|
||||||
|
* @param theTargetResourceName the type of the target
|
||||||
|
* @param theIdParams the id(s) to verify exist
|
||||||
|
*/
|
||||||
|
public void validateTargetsExists(
|
||||||
|
@Nonnull RequestDetails theRequestDetails,
|
||||||
|
@Nonnull String theTargetResourceName,
|
||||||
|
@Nonnull Iterable<IIdType> theIdParams) {
|
||||||
|
if (theIdParams.iterator().hasNext()) {
|
||||||
|
RequestPartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForRead(
|
||||||
|
theRequestDetails,
|
||||||
|
theTargetResourceName,
|
||||||
|
theIdParams.iterator().next());
|
||||||
|
SystemRequestDetails requestDetails = new SystemRequestDetails().setRequestPartitionId(partitionId);
|
||||||
|
for (IIdType nextId : theIdParams) {
|
||||||
|
myDaoRegistry.getResourceDao(theTargetResourceName).read(nextId, requestDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateOrDefaultResourceTypesForGroupBulkExport(
|
||||||
|
@Nonnull BulkExportJobParameters theBulkExportJobParameters) {
|
||||||
|
if (CollectionUtils.isNotEmpty(theBulkExportJobParameters.getResourceTypes())) {
|
||||||
|
validateResourceTypesAllContainPatientSearchParams(theBulkExportJobParameters.getResourceTypes());
|
||||||
|
} else {
|
||||||
|
// all patient resource types
|
||||||
|
Set<String> groupTypes = new HashSet<>(getPatientCompartmentResources());
|
||||||
|
|
||||||
|
// Add the forward reference resource types from the patients, e.g. Practitioner, Organization
|
||||||
|
groupTypes.addAll(BulkDataExportUtil.PATIENT_BULK_EXPORT_FORWARD_REFERENCE_RESOURCE_TYPES);
|
||||||
|
|
||||||
|
groupTypes.removeIf(t -> !myDaoRegistry.isResourceTypeSupported(t));
|
||||||
|
theBulkExportJobParameters.setResourceTypes(groupTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateResourceTypesAllContainPatientSearchParams(Collection<String> theResourceTypes) {
|
||||||
|
if (theResourceTypes != null) {
|
||||||
|
List<String> badResourceTypes = theResourceTypes.stream()
|
||||||
|
.filter(resourceType ->
|
||||||
|
!BulkDataExportUtil.PATIENT_BULK_EXPORT_FORWARD_REFERENCE_RESOURCE_TYPES.contains(
|
||||||
|
resourceType))
|
||||||
|
.filter(resourceType -> !getPatientCompartmentResources().contains(resourceType))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!badResourceTypes.isEmpty()) {
|
||||||
|
throw new InvalidRequestException(Msg.code(512)
|
||||||
|
+ String.format(
|
||||||
|
"Resource types [%s] are invalid for this type of export, as they do not contain search parameters that refer to patients.",
|
||||||
|
String.join(",", badResourceTypes)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getPatientCompartmentResources() {
|
||||||
|
return getPatientCompartmentResources(myFhirContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> getPatientCompartmentResources(FhirContext theFhirContext) {
|
||||||
|
if (myCompartmentResources == null) {
|
||||||
|
myCompartmentResources =
|
||||||
|
new HashSet<>(SearchParameterUtil.getAllResourceTypesThatAreInPatientCompartment(theFhirContext));
|
||||||
|
}
|
||||||
|
return myCompartmentResources;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package ca.uhn.fhir.batch2.jobs.export;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.PreferHeader;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BulkDataExportUtil {
|
||||||
|
public static final List<String> PATIENT_BULK_EXPORT_FORWARD_REFERENCE_RESOURCE_TYPES =
|
||||||
|
List.of("Practitioner", "Organization");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk data $export does not include the Binary type
|
||||||
|
*/
|
||||||
|
public static final String UNSUPPORTED_BINARY_TYPE = "Binary";
|
||||||
|
|
||||||
|
private BulkDataExportUtil() {
|
||||||
|
// non-instantiable
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validatePreferAsyncHeader(ServletRequestDetails theRequestDetails, String theOperationName) {
|
||||||
|
String preferHeader = theRequestDetails.getHeader(Constants.HEADER_PREFER);
|
||||||
|
PreferHeader prefer = RestfulServerUtils.parsePreferHeader(null, preferHeader);
|
||||||
|
if (!prefer.getRespondAsync()) {
|
||||||
|
throw new InvalidRequestException(Msg.code(513) + "Must request async processing for " + theOperationName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getServerBase(ServletRequestDetails theRequestDetails) {
|
||||||
|
return StringUtils.removeEnd(theRequestDetails.getServerBaseForRequest(), "/");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package ca.uhn.fhir.batch2.jobs.export;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
|
||||||
|
import ca.uhn.fhir.util.ArrayUtil;
|
||||||
|
import ca.uhn.fhir.util.DatatypeUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a Builder class which helps with building
|
||||||
|
* BulkExportJobParameters object
|
||||||
|
*/
|
||||||
|
public class BulkExportJobParametersBuilder {
|
||||||
|
public static final String FARM_TO_TABLE_TYPE_FILTER_REGEX = "(?:,)(?=[A-Z][a-z]+\\?)";
|
||||||
|
|
||||||
|
private Set<String> myResourceTypes;
|
||||||
|
private Date mySince;
|
||||||
|
private Date myUntil;
|
||||||
|
private Set<String> myFilters;
|
||||||
|
private String myOutputFormat;
|
||||||
|
private BulkExportJobParameters.ExportStyle myExportStyle;
|
||||||
|
private List<String> myPatientIds = new ArrayList<>();
|
||||||
|
private String myGroupId;
|
||||||
|
private boolean myExpandMdm;
|
||||||
|
private RequestPartitionId myPartitionId;
|
||||||
|
private String myExportIdentifier;
|
||||||
|
private Set<String> myPostFetchFilterUrls;
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder resourceTypes(IPrimitiveType<String> theResourceTypes) {
|
||||||
|
myResourceTypes = theResourceTypes == null
|
||||||
|
? null
|
||||||
|
: ArrayUtil.commaSeparatedListToCleanSet(theResourceTypes.getValueAsString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder since(IPrimitiveType<Date> theSince) {
|
||||||
|
mySince = DatatypeUtil.toDateValue(theSince);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder until(IPrimitiveType<Date> theUntil) {
|
||||||
|
myUntil = DatatypeUtil.toDateValue(theUntil);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder filters(List<IPrimitiveType<String>> theFilters) {
|
||||||
|
myFilters = parseFilters(theFilters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder outputFormat(IPrimitiveType<String> theOutputFormat) {
|
||||||
|
myOutputFormat = theOutputFormat != null ? theOutputFormat.getValueAsString() : Constants.CT_FHIR_NDJSON;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder exportStyle(BulkExportJobParameters.ExportStyle theExportStyle) {
|
||||||
|
myExportStyle = theExportStyle;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder patientIds(List<IPrimitiveType<String>> thePatientIds) {
|
||||||
|
myPatientIds = thePatientIds == null
|
||||||
|
? null
|
||||||
|
: thePatientIds.stream().map(IPrimitiveType::getValueAsString).collect(Collectors.toList());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder groupId(IIdType theGroupId) {
|
||||||
|
myGroupId = DatatypeUtil.toStringValue(theGroupId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder expandMdm(IPrimitiveType<Boolean> theExpandMdm) {
|
||||||
|
final Boolean booleanValue = DatatypeUtil.toBooleanValue(theExpandMdm);
|
||||||
|
myExpandMdm = booleanValue != null && booleanValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder partitionId(RequestPartitionId thePartitionId) {
|
||||||
|
myPartitionId = thePartitionId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder exportIdentifier(IPrimitiveType<String> theExportIdentifier) {
|
||||||
|
myExportIdentifier = DatatypeUtil.toStringValue(theExportIdentifier);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParametersBuilder postFetchFilterUrl(List<IPrimitiveType<String>> thePostFetchFilterUrl) {
|
||||||
|
myPostFetchFilterUrls = parseFilters(thePostFetchFilterUrl);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkExportJobParameters build() {
|
||||||
|
BulkExportJobParameters result = new BulkExportJobParameters();
|
||||||
|
result.setExpandMdm(myExpandMdm);
|
||||||
|
result.setExportIdentifier(myExportIdentifier);
|
||||||
|
result.setExportStyle(myExportStyle);
|
||||||
|
result.setFilters(myFilters);
|
||||||
|
result.setGroupId(myGroupId);
|
||||||
|
result.setOutputFormat(myOutputFormat);
|
||||||
|
result.setPartitionId(myPartitionId);
|
||||||
|
result.setPatientIds(myPatientIds);
|
||||||
|
result.setResourceTypes(myResourceTypes);
|
||||||
|
result.setSince(mySince);
|
||||||
|
result.setUntil(myUntil);
|
||||||
|
result.setPostFetchFilterUrls(myPostFetchFilterUrls);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> parseFilters(List<IPrimitiveType<String>> theFilters) {
|
||||||
|
Set<String> retVal = null;
|
||||||
|
if (theFilters != null) {
|
||||||
|
retVal = new HashSet<>();
|
||||||
|
for (IPrimitiveType<String> next : theFilters) {
|
||||||
|
String typeFilterString = next.getValueAsString();
|
||||||
|
Arrays.stream(typeFilterString.split(FARM_TO_TABLE_TYPE_FILTER_REGEX))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.forEach(retVal::add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,9 +40,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
public class BulkExportJobParametersValidator implements IJobParametersValidator<BulkExportJobParameters> {
|
public class BulkExportJobParametersValidator implements IJobParametersValidator<BulkExportJobParameters> {
|
||||||
|
|
||||||
/** @deprecated use BulkDataExportProvider.UNSUPPORTED_BINARY_TYPE instead */
|
/** @deprecated use BulkDataExportUtil.UNSUPPORTED_BINARY_TYPE instead */
|
||||||
@Deprecated(since = "6.3.10")
|
@Deprecated(since = "6.3.10")
|
||||||
public static final String UNSUPPORTED_BINARY_TYPE = BulkDataExportProvider.UNSUPPORTED_BINARY_TYPE;
|
public static final String UNSUPPORTED_BINARY_TYPE = BulkDataExportUtil.UNSUPPORTED_BINARY_TYPE;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoRegistry myDaoRegistry;
|
private DaoRegistry myDaoRegistry;
|
||||||
|
@ -62,7 +62,7 @@ public class BulkExportJobParametersValidator implements IJobParametersValidator
|
||||||
List<String> resourceTypes = theParameters.getResourceTypes();
|
List<String> resourceTypes = theParameters.getResourceTypes();
|
||||||
if (resourceTypes != null && !resourceTypes.isEmpty()) {
|
if (resourceTypes != null && !resourceTypes.isEmpty()) {
|
||||||
for (String resourceType : theParameters.getResourceTypes()) {
|
for (String resourceType : theParameters.getResourceTypes()) {
|
||||||
if (resourceType.equalsIgnoreCase(UNSUPPORTED_BINARY_TYPE)) {
|
if (resourceType.equalsIgnoreCase(BulkDataExportUtil.UNSUPPORTED_BINARY_TYPE)) {
|
||||||
errorMsgs.add("Bulk export of Binary resources is forbidden");
|
errorMsgs.add("Bulk export of Binary resources is forbidden");
|
||||||
} else if (!myDaoRegistry.isResourceTypeSupported(resourceType)) {
|
} else if (!myDaoRegistry.isResourceTypeSupported(resourceType)) {
|
||||||
errorMsgs.add("Resource type " + resourceType + " is not a supported resource type!");
|
errorMsgs.add("Resource type " + resourceType + " is not a supported resource type!");
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
package ca.uhn.fhir.batch2.jobs.export;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.batch2.api.IJobCoordinator;
|
||||||
|
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
|
||||||
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
|
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.batch.models.Batch2JobStartResponse;
|
||||||
|
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||||
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||||
|
import ca.uhn.fhir.util.Batch2JobDefinitionConstants;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for initiating a bulk export job
|
||||||
|
* with appropriate _type parameter & partitionId as well as
|
||||||
|
* generating response for request which includes the polling location.
|
||||||
|
* It also calls hooks which can update BulkExportJobParameters and the incoming requests.
|
||||||
|
*/
|
||||||
|
public class BulkExportJobService {
|
||||||
|
private final IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
private final IJobCoordinator myJobCoordinator;
|
||||||
|
private final DaoRegistry myDaoRegistry;
|
||||||
|
private final IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||||
|
private final JpaStorageSettings myStorageSettings;
|
||||||
|
|
||||||
|
public BulkExportJobService(
|
||||||
|
@Nonnull IInterceptorBroadcaster theInterceptorBroadcaster,
|
||||||
|
@Nonnull IJobCoordinator theJobCoordinator,
|
||||||
|
@Nonnull DaoRegistry theDaoRegistry,
|
||||||
|
@Nonnull IRequestPartitionHelperSvc theRequestPartitionHelperService,
|
||||||
|
@Nonnull JpaStorageSettings theStorageSettings) {
|
||||||
|
myInterceptorBroadcaster = theInterceptorBroadcaster;
|
||||||
|
myJobCoordinator = theJobCoordinator;
|
||||||
|
myDaoRegistry = theDaoRegistry;
|
||||||
|
myRequestPartitionHelperService = theRequestPartitionHelperService;
|
||||||
|
myStorageSettings = theStorageSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start BulkExport job with appropriate parameters
|
||||||
|
*/
|
||||||
|
public void startJob(
|
||||||
|
@Nonnull ServletRequestDetails theRequestDetails,
|
||||||
|
@Nonnull BulkExportJobParameters theBulkExportJobParameters) {
|
||||||
|
// parameter massaging
|
||||||
|
expandParameters(theRequestDetails, theBulkExportJobParameters);
|
||||||
|
callBulkExportHooks(theRequestDetails, theBulkExportJobParameters);
|
||||||
|
|
||||||
|
// get cache boolean
|
||||||
|
boolean useCache = shouldUseCache(theRequestDetails);
|
||||||
|
|
||||||
|
// start job
|
||||||
|
JobInstanceStartRequest startRequest = new JobInstanceStartRequest();
|
||||||
|
startRequest.setParameters(theBulkExportJobParameters);
|
||||||
|
startRequest.setUseCache(useCache);
|
||||||
|
startRequest.setJobDefinitionId(Batch2JobDefinitionConstants.BULK_EXPORT);
|
||||||
|
Batch2JobStartResponse response = myJobCoordinator.startInstance(theRequestDetails, startRequest);
|
||||||
|
|
||||||
|
writePollingLocationToResponseHeaders(theRequestDetails, response.getInstanceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method changes any parameters (limiting the _type parameter, for instance)
|
||||||
|
* so that later steps in the export do not have to handle them.
|
||||||
|
*/
|
||||||
|
private void expandParameters(
|
||||||
|
@Nonnull ServletRequestDetails theRequestDetails,
|
||||||
|
@Nonnull BulkExportJobParameters theBulkExportJobParameters) {
|
||||||
|
// Set the original request URL as part of the job information, as this is used in the poll-status-endpoint, and
|
||||||
|
// is needed for the report.
|
||||||
|
theBulkExportJobParameters.setOriginalRequestUrl(theRequestDetails.getCompleteUrl());
|
||||||
|
|
||||||
|
// If no _type parameter is provided, default to all resource types except Binary
|
||||||
|
if (theBulkExportJobParameters.getResourceTypes().isEmpty()) {
|
||||||
|
List<String> resourceTypes = new ArrayList<>(myDaoRegistry.getRegisteredDaoTypes());
|
||||||
|
resourceTypes.remove(BulkDataExportUtil.UNSUPPORTED_BINARY_TYPE);
|
||||||
|
theBulkExportJobParameters.setResourceTypes(resourceTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine and validate partition permissions (if needed).
|
||||||
|
RequestPartitionId partitionId =
|
||||||
|
myRequestPartitionHelperService.determineReadPartitionForRequestForServerOperation(
|
||||||
|
theRequestDetails, ProviderConstants.OPERATION_EXPORT);
|
||||||
|
myRequestPartitionHelperService.validateHasPartitionPermissions(theRequestDetails, "Binary", partitionId);
|
||||||
|
theBulkExportJobParameters.setPartitionId(partitionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method calls STORAGE_PRE_INITIATE_BULK_EXPORT & STORAGE_INITIATE_BULK_EXPORT,
|
||||||
|
* if present, which allows modification to the request and the bulk export job parameters
|
||||||
|
*/
|
||||||
|
private void callBulkExportHooks(
|
||||||
|
@Nonnull ServletRequestDetails theRequestDetails,
|
||||||
|
@Nonnull BulkExportJobParameters theBulkExportJobParameters) {
|
||||||
|
IInterceptorBroadcaster compositeBroadcaster =
|
||||||
|
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequestDetails);
|
||||||
|
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PRE_INITIATE_BULK_EXPORT)) {
|
||||||
|
HookParams preInitiateBulkExportHookParams = new HookParams()
|
||||||
|
.add(BulkExportJobParameters.class, theBulkExportJobParameters)
|
||||||
|
.add(RequestDetails.class, theRequestDetails)
|
||||||
|
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
|
||||||
|
compositeBroadcaster.callHooks(Pointcut.STORAGE_PRE_INITIATE_BULK_EXPORT, preInitiateBulkExportHookParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_INITIATE_BULK_EXPORT)) {
|
||||||
|
HookParams initiateBulkExportHookParams = (new HookParams())
|
||||||
|
.add(BulkExportJobParameters.class, theBulkExportJobParameters)
|
||||||
|
.add(RequestDetails.class, theRequestDetails)
|
||||||
|
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
|
||||||
|
compositeBroadcaster.callHooks(Pointcut.STORAGE_INITIATE_BULK_EXPORT, initiateBulkExportHookParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if the request has the cache-control header
|
||||||
|
* set to no-cache
|
||||||
|
*/
|
||||||
|
private boolean shouldUseCache(@Nonnull ServletRequestDetails theRequestDetails) {
|
||||||
|
CacheControlDirective cacheControlDirective =
|
||||||
|
new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL));
|
||||||
|
return myStorageSettings.getEnableBulkExportJobReuse() && !cacheControlDirective.isNoCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method generates response for the bulk export request
|
||||||
|
* which contains the polling location
|
||||||
|
*/
|
||||||
|
private void writePollingLocationToResponseHeaders(
|
||||||
|
@Nonnull ServletRequestDetails theRequestDetails, @Nonnull String theInstanceId) {
|
||||||
|
String serverBase = BulkDataExportUtil.getServerBase(theRequestDetails);
|
||||||
|
if (serverBase == null) {
|
||||||
|
throw new InternalErrorException(Msg.code(2136) + "Unable to get the server base.");
|
||||||
|
}
|
||||||
|
String pollLocation = serverBase + "/" + ProviderConstants.OPERATION_EXPORT_POLL_STATUS + "?"
|
||||||
|
+ JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + theInstanceId;
|
||||||
|
pollLocation = UrlUtil.sanitizeHeaderValue(pollLocation);
|
||||||
|
|
||||||
|
HttpServletResponse response = theRequestDetails.getServletResponse();
|
||||||
|
|
||||||
|
// Add standard headers
|
||||||
|
theRequestDetails.getServer().addHeadersToResponse(response);
|
||||||
|
|
||||||
|
// Successful 202 Accepted
|
||||||
|
response.addHeader(Constants.HEADER_CONTENT_LOCATION, pollLocation);
|
||||||
|
response.setStatus(Constants.STATUS_HTTP_202_ACCEPTED);
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,6 +70,7 @@ public class FetchResourceIdsStep implements IFirstJobStepWorker<BulkExportJobPa
|
||||||
providerParams.setChunkId(theStepExecutionDetails.getChunkId());
|
providerParams.setChunkId(theStepExecutionDetails.getChunkId());
|
||||||
providerParams.setFilters(params.getFilters());
|
providerParams.setFilters(params.getFilters());
|
||||||
providerParams.setStartDate(params.getSince());
|
providerParams.setStartDate(params.getSince());
|
||||||
|
providerParams.setEndDate(params.getUntil());
|
||||||
providerParams.setExportStyle(params.getExportStyle());
|
providerParams.setExportStyle(params.getExportStyle());
|
||||||
providerParams.setGroupId(params.getGroupId());
|
providerParams.setGroupId(params.getGroupId());
|
||||||
providerParams.setPatientIds(params.getPatientIds());
|
providerParams.setPatientIds(params.getPatientIds());
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package ca.uhn.fhir.batch2.jobs.imprt;
|
package ca.uhn.fhir.batch2.jobs.imprt;
|
||||||
|
|
||||||
import ca.uhn.fhir.batch2.api.IJobCoordinator;
|
import ca.uhn.fhir.batch2.api.IJobCoordinator;
|
||||||
|
import ca.uhn.fhir.batch2.jobs.export.BulkDataExportUtil;
|
||||||
import ca.uhn.fhir.batch2.model.JobInstance;
|
import ca.uhn.fhir.batch2.model.JobInstance;
|
||||||
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
|
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
@ -59,7 +60,6 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static ca.uhn.fhir.batch2.jobs.export.BulkDataExportProvider.validatePreferAsyncHeader;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public class BulkDataImportProvider {
|
public class BulkDataImportProvider {
|
||||||
|
@ -127,7 +127,7 @@ public class BulkDataImportProvider {
|
||||||
HttpServletResponse theResponse)
|
HttpServletResponse theResponse)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
validatePreferAsyncHeader(theRequestDetails, JpaConstants.OPERATION_IMPORT);
|
BulkDataExportUtil.validatePreferAsyncHeader(theRequestDetails, JpaConstants.OPERATION_IMPORT);
|
||||||
|
|
||||||
BulkImportJobParameters jobParameters = new BulkImportJobParameters();
|
BulkImportJobParameters jobParameters = new BulkImportJobParameters();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package ca.uhn.fhir.batch2.jobs.export;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.EnumSource;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class BulkDataExportUtilTest {
|
||||||
|
private static final String URL = "http://localhost:8080";
|
||||||
|
private static final String OPERATION_NAME = "Operation Name";
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ServletRequestDetails theRequestDetails;
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
verifyNoMoreInteractions(theRequestDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(value = PreferReturnEnum.class)
|
||||||
|
void validatePreferAsyncHeaderShouldThrowException(PreferReturnEnum thePreferReturnEnum) {
|
||||||
|
// Arrange
|
||||||
|
doReturn(thePreferReturnEnum.getHeaderValue()).when(theRequestDetails).getHeader(Constants.HEADER_PREFER);
|
||||||
|
// Act
|
||||||
|
assertThatThrownBy(() -> BulkDataExportUtil.validatePreferAsyncHeader(theRequestDetails, OPERATION_NAME))
|
||||||
|
.isInstanceOf(InvalidRequestException.class)
|
||||||
|
.hasMessageContaining("Must request async processing for " + OPERATION_NAME);
|
||||||
|
// Assert
|
||||||
|
verify(theRequestDetails).getHeader(Constants.HEADER_PREFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validatePreferAsyncHeaderShouldNotThrowException() {
|
||||||
|
// Arrange
|
||||||
|
doReturn(Constants.HEADER_PREFER_RESPOND_ASYNC).when(theRequestDetails).getHeader(Constants.HEADER_PREFER);
|
||||||
|
// Act
|
||||||
|
assertThatNoException().isThrownBy(() -> BulkDataExportUtil.validatePreferAsyncHeader(theRequestDetails, OPERATION_NAME));
|
||||||
|
// Assert
|
||||||
|
verify(theRequestDetails).getHeader(Constants.HEADER_PREFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getServerBase() {
|
||||||
|
// Arrange
|
||||||
|
doReturn(URL + "/").when(theRequestDetails).getServerBaseForRequest();
|
||||||
|
// Act
|
||||||
|
final String actual = BulkDataExportUtil.getServerBase(theRequestDetails);
|
||||||
|
// Assert
|
||||||
|
assertThat(actual).isEqualTo(URL);
|
||||||
|
verify(theRequestDetails).getServerBaseForRequest();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
package ca.uhn.fhir.batch2.jobs.export;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.model.primitive.BooleanDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.DateDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
|
||||||
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class BulkExportJobParametersBuilderTest {
|
||||||
|
|
||||||
|
private final BulkExportJobParametersBuilder myFixture = new BulkExportJobParametersBuilder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resourceTypes() {
|
||||||
|
// Arrange
|
||||||
|
final List<String> expected = List.of("Patient", "Observation", "MedicationRequest");
|
||||||
|
final IPrimitiveType<String> resourceTypes = new StringDt(String.join(",", expected));
|
||||||
|
// Act
|
||||||
|
myFixture.resourceTypes(resourceTypes);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getResourceTypes()).containsAll(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resourceTypesWhenNull() {
|
||||||
|
// Act
|
||||||
|
myFixture.resourceTypes(null);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getResourceTypes()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void since() {
|
||||||
|
// Arrange
|
||||||
|
final Date expected = new Date();
|
||||||
|
final IPrimitiveType<Date> since = new DateDt(expected);
|
||||||
|
// Act
|
||||||
|
myFixture.since(since);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getSince()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void until() {
|
||||||
|
// Arrange
|
||||||
|
final Date expected = new Date();
|
||||||
|
final IPrimitiveType<Date> until = new DateDt(expected);
|
||||||
|
// Act
|
||||||
|
myFixture.until(until);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getUntil()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void filters() {
|
||||||
|
// Arrange
|
||||||
|
final List<String> expected = List.of("Patient", "Observation", "MedicationRequest");
|
||||||
|
final List<IPrimitiveType<String>> filters = expected.stream().map(value -> (IPrimitiveType<String>) new StringDt(value)).toList();
|
||||||
|
// Act
|
||||||
|
myFixture.filters(filters);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getFilters()).containsAll(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void filtersWhenNull() {
|
||||||
|
// Act
|
||||||
|
myFixture.filters(null);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getFilters()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void outputFormat() {
|
||||||
|
// Arrange
|
||||||
|
final String expected = "some value";
|
||||||
|
final IPrimitiveType<String> outputFormat = new StringDt(expected);
|
||||||
|
// Act
|
||||||
|
myFixture.outputFormat(outputFormat);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getOutputFormat()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void outputFormatWhenNull() {
|
||||||
|
// Act
|
||||||
|
myFixture.outputFormat(null);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getOutputFormat()).isEqualTo(Constants.CT_FHIR_NDJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exportStyle() {
|
||||||
|
// Arrange
|
||||||
|
final BulkExportJobParameters.ExportStyle expected = BulkExportJobParameters.ExportStyle.SYSTEM;
|
||||||
|
// Act
|
||||||
|
myFixture.exportStyle(expected);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getExportStyle()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void patientIds() {
|
||||||
|
// Arrange
|
||||||
|
final List<String> expected = List.of("ID1", "ID2", "ID3");
|
||||||
|
final List<IPrimitiveType<String>> patientIds = expected.stream().map(value -> (IPrimitiveType<String>) new StringDt(value)).toList();
|
||||||
|
// Act
|
||||||
|
myFixture.patientIds(patientIds);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getPatientIds()).containsAll(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void patientIdsWhenNull() {
|
||||||
|
// Act
|
||||||
|
myFixture.patientIds(null);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getPatientIds()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void groupId() {
|
||||||
|
// Arrange
|
||||||
|
final String expected = "GROUP_ID";
|
||||||
|
final IdType groupId = new IdType(expected);
|
||||||
|
// Act
|
||||||
|
myFixture.groupId(groupId);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getGroupId()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void expandMdm() {
|
||||||
|
// Arrange
|
||||||
|
final IPrimitiveType<Boolean> expandMdm = new BooleanDt(Boolean.TRUE);
|
||||||
|
// Act
|
||||||
|
myFixture.expandMdm(expandMdm);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().isExpandMdm()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void expandMdmWhenNull() {
|
||||||
|
// Act
|
||||||
|
myFixture.expandMdm(null);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().isExpandMdm()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void partitionId() {
|
||||||
|
// Arrange
|
||||||
|
final RequestPartitionId expected = RequestPartitionId.fromPartitionName("PARTITION_NAME");
|
||||||
|
// Act
|
||||||
|
myFixture.partitionId(expected);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getPartitionId()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exportIdentifier() {
|
||||||
|
// Arrange
|
||||||
|
final String expected = "EXPORT_IDENTIFIER";
|
||||||
|
final StringDt exportIdentifier = new StringDt(expected);
|
||||||
|
// Act
|
||||||
|
myFixture.exportIdentifier(exportIdentifier);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getExportIdentifier()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void postFetchFilterUrl() {
|
||||||
|
// Arrange
|
||||||
|
final List<String> expected = List.of("URL1", "URL2", "URL3");
|
||||||
|
final List<IPrimitiveType<String>> postFetchFilterUrls = expected.stream().map(value -> (IPrimitiveType<String>) new StringDt(value)).toList();
|
||||||
|
// Act
|
||||||
|
myFixture.postFetchFilterUrl(postFetchFilterUrls);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getPostFetchFilterUrls()).containsAll(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void postFetchFilterUrlWhenNull() {
|
||||||
|
// Act
|
||||||
|
myFixture.postFetchFilterUrl(null);
|
||||||
|
// Assert
|
||||||
|
assertThat(myFixture.build().getPostFetchFilterUrls()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -41,6 +41,11 @@ public class ExportPIDIteratorParameters {
|
||||||
*/
|
*/
|
||||||
private Date myStartDate;
|
private Date myStartDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest date to which to retrieve records
|
||||||
|
*/
|
||||||
|
private Date myEndDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of filters to be applied to the search.
|
* List of filters to be applied to the search.
|
||||||
* Eg:
|
* Eg:
|
||||||
|
@ -108,6 +113,14 @@ public class ExportPIDIteratorParameters {
|
||||||
myStartDate = theStartDate;
|
myStartDate = theStartDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Date getEndDate() {
|
||||||
|
return myEndDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndDate(Date theEndDate) {
|
||||||
|
myEndDate = theEndDate;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getFilters() {
|
public List<String> getFilters() {
|
||||||
return myFilters;
|
return myFilters;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,16 +48,17 @@ public class BulkExportHelperService {
|
||||||
/**
|
/**
|
||||||
* Given the parameters, create the search parameter map based on type filters and the _since parameter.
|
* Given the parameters, create the search parameter map based on type filters and the _since parameter.
|
||||||
*
|
*
|
||||||
* The input boolean theConsiderSince determines whether to consider the lastUpdated date in the search parameter map.
|
* The input boolean theConsiderDateRange determines whether to consider the lastUpdated date in the search parameter map.
|
||||||
*/
|
*/
|
||||||
public List<SearchParameterMap> createSearchParameterMapsForResourceType(
|
public List<SearchParameterMap> createSearchParameterMapsForResourceType(
|
||||||
RuntimeResourceDefinition theDef, ExportPIDIteratorParameters theParams, boolean theConsiderSince) {
|
RuntimeResourceDefinition theDef, ExportPIDIteratorParameters theParams, boolean theConsiderDateRange) {
|
||||||
String resourceType = theDef.getName();
|
String resourceType = theDef.getName();
|
||||||
List<String> typeFilters = theParams.getFilters();
|
List<String> typeFilters = theParams.getFilters();
|
||||||
List<SearchParameterMap> spMaps = null;
|
List<SearchParameterMap> spMaps = null;
|
||||||
spMaps = typeFilters.stream()
|
spMaps = typeFilters.stream()
|
||||||
.filter(typeFilter -> typeFilter.startsWith(resourceType + "?"))
|
.filter(typeFilter -> typeFilter.startsWith(resourceType + "?"))
|
||||||
.map(filter -> buildSearchParameterMapForTypeFilter(filter, theDef, theParams.getStartDate()))
|
.map(filter -> buildSearchParameterMapForTypeFilter(
|
||||||
|
filter, theDef, theParams.getStartDate(), theParams.getEndDate()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
typeFilters.stream().filter(filter -> !filter.contains("?")).forEach(filter -> {
|
typeFilters.stream().filter(filter -> !filter.contains("?")).forEach(filter -> {
|
||||||
|
@ -69,8 +70,8 @@ public class BulkExportHelperService {
|
||||||
// None of the _typeFilters applied to the current resource type, so just make a simple one.
|
// None of the _typeFilters applied to the current resource type, so just make a simple one.
|
||||||
if (spMaps.isEmpty()) {
|
if (spMaps.isEmpty()) {
|
||||||
SearchParameterMap defaultMap = new SearchParameterMap();
|
SearchParameterMap defaultMap = new SearchParameterMap();
|
||||||
if (theConsiderSince) {
|
if (theConsiderDateRange) {
|
||||||
enhanceSearchParameterMapWithCommonParameters(defaultMap, theParams.getStartDate());
|
addLastUpdatedFilter(defaultMap, theParams.getStartDate(), theParams.getEndDate());
|
||||||
}
|
}
|
||||||
spMaps = Collections.singletonList(defaultMap);
|
spMaps = Collections.singletonList(defaultMap);
|
||||||
}
|
}
|
||||||
|
@ -79,16 +80,16 @@ public class BulkExportHelperService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SearchParameterMap buildSearchParameterMapForTypeFilter(
|
private SearchParameterMap buildSearchParameterMapForTypeFilter(
|
||||||
String theFilter, RuntimeResourceDefinition theDef, Date theSinceDate) {
|
String theFilter, RuntimeResourceDefinition theDef, Date theStartDate, Date theEndDate) {
|
||||||
SearchParameterMap searchParameterMap = myMatchUrlService.translateMatchUrl(theFilter, theDef);
|
SearchParameterMap searchParameterMap = myMatchUrlService.translateMatchUrl(theFilter, theDef);
|
||||||
enhanceSearchParameterMapWithCommonParameters(searchParameterMap, theSinceDate);
|
addLastUpdatedFilter(searchParameterMap, theStartDate, theEndDate);
|
||||||
return searchParameterMap;
|
return searchParameterMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enhanceSearchParameterMapWithCommonParameters(SearchParameterMap map, Date theSinceDate) {
|
void addLastUpdatedFilter(SearchParameterMap map, Date theStartDate, Date theEndDate) {
|
||||||
map.setLoadSynchronous(true);
|
map.setLoadSynchronous(true);
|
||||||
if (theSinceDate != null) {
|
if (theStartDate != null || theEndDate != null) {
|
||||||
map.setLastUpdated(new DateRangeParam(theSinceDate, null));
|
map.setLastUpdated(new DateRangeParam(theStartDate, theEndDate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue