Clean up handling of searches nested in batch and transaction
This commit is contained in:
parent
28a5b92fe2
commit
c9fcef0372
|
@ -1744,9 +1744,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res);
|
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res);
|
||||||
if (tagList != null) {
|
if (tagList != null) {
|
||||||
tag = tagList.getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE);
|
tag = tagList.getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE);
|
||||||
|
totalMetaCount += tagList.size();
|
||||||
|
}
|
||||||
|
List<IdDt> profileList = ResourceMetadataKeyEnum.PROFILES.get(res);
|
||||||
|
if (profileList != null) {
|
||||||
|
totalMetaCount += profileList.size();
|
||||||
}
|
}
|
||||||
totalMetaCount += tagList.size();
|
|
||||||
totalMetaCount += ResourceMetadataKeyEnum.PROFILES.get(res).size();
|
|
||||||
} else {
|
} else {
|
||||||
IAnyResource res = (IAnyResource) theResource;
|
IAnyResource res = (IAnyResource) theResource;
|
||||||
tag = res.getMeta().getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE);
|
tag = res.getMeta().getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE);
|
||||||
|
|
|
@ -29,6 +29,7 @@ import javax.annotation.PostConstruct;
|
||||||
import javax.persistence.NoResultException;
|
import javax.persistence.NoResultException;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.dstu3.model.IdType;
|
import org.hl7.fhir.dstu3.model.IdType;
|
||||||
import org.hl7.fhir.instance.model.api.*;
|
import org.hl7.fhir.instance.model.api.*;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -926,9 +927,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails);
|
notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails);
|
||||||
|
|
||||||
if (theRequestDetails.isSubRequest()) {
|
if (theRequestDetails.isSubRequest()) {
|
||||||
theParams.setLoadSynchronous(true);
|
Integer max = myDaoConfig.getMaximumSearchResultCountInTransaction();
|
||||||
int max = myDaoConfig.getMaximumSearchResultCountInTransaction();
|
if (max != null) {
|
||||||
theParams.setLoadSynchronousUpTo(myDaoConfig.getMaximumSearchResultCountInTransaction());
|
Validate.inclusiveBetween(1, Integer.MAX_VALUE, max.intValue(), "Maximum search result count in transaction ust be a positive integer");
|
||||||
|
theParams.setLoadSynchronousUpTo(myDaoConfig.getMaximumSearchResultCountInTransaction());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPagingProviderDatabaseBacked(theRequestDetails)) {
|
if (!isPagingProviderDatabaseBacked(theRequestDetails)) {
|
||||||
|
|
|
@ -57,7 +57,7 @@ public class DaoConfig {
|
||||||
*
|
*
|
||||||
* @see #setMaximumSearchResultCountInTransaction(int)
|
* @see #setMaximumSearchResultCountInTransaction(int)
|
||||||
*/
|
*/
|
||||||
private static final int DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION = 500;
|
private static final Integer DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default value for {@link #setReuseCachedSearchResultsForMillis(Long)}: 60000ms (one minute)
|
* Default value for {@link #setReuseCachedSearchResultsForMillis(Long)}: 60000ms (one minute)
|
||||||
|
@ -110,7 +110,7 @@ public class DaoConfig {
|
||||||
* update setter javadoc if default changes
|
* update setter javadoc if default changes
|
||||||
*/
|
*/
|
||||||
private int myMaximumExpansionSize = 5000;
|
private int myMaximumExpansionSize = 5000;
|
||||||
private int myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION;
|
private Integer myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION;
|
||||||
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
|
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
|
||||||
/**
|
/**
|
||||||
* update setter javadoc if default changes
|
* update setter javadoc if default changes
|
||||||
|
@ -233,14 +233,17 @@ public class DaoConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the maximum number of results which may be returned by a search within a FHIR <code>transaction</code>
|
* Provides the maximum number of results which may be returned by a search (HTTP GET) which
|
||||||
* operation. For example, if this value is set to <code>100</code> and a FHIR transaction is processed with a sub-request
|
* is executed as a sub-operation within within a FHIR <code>transaction</code> or
|
||||||
* for <code>Patient?gender=male</code>, the server will throw an error (and the transaction will fail) if there are more than
|
* <code>batch</code> operation. For example, if this value is set to <code>100</code> and
|
||||||
|
* a FHIR transaction is processed with a sub-request for <code>Patient?gender=male</code>,
|
||||||
|
* the server will throw an error (and the transaction will fail) if there are more than
|
||||||
* 100 resources on the server which match this query.
|
* 100 resources on the server which match this query.
|
||||||
*
|
* <p>
|
||||||
* @see #DEFAULT_LOGICAL_BASE_URLS The default value for this setting
|
* The default value is <code>null</code>, which means that there is no limit.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public int getMaximumSearchResultCountInTransaction() {
|
public Integer getMaximumSearchResultCountInTransaction() {
|
||||||
return myMaximumSearchResultCountInTransaction;
|
return myMaximumSearchResultCountInTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,14 +702,17 @@ public class DaoConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the maximum number of results which may be returned by a search within a FHIR <code>transaction</code>
|
* Provides the maximum number of results which may be returned by a search (HTTP GET) which
|
||||||
* operation. For example, if this value is set to <code>100</code> and a FHIR transaction is processed with a sub-request
|
* is executed as a sub-operation within within a FHIR <code>transaction</code> or
|
||||||
* for <code>Patient?gender=male</code>, the server will throw an error (and the transaction will fail) if there are more than
|
* <code>batch</code> operation. For example, if this value is set to <code>100</code> and
|
||||||
|
* a FHIR transaction is processed with a sub-request for <code>Patient?gender=male</code>,
|
||||||
|
* the server will throw an error (and the transaction will fail) if there are more than
|
||||||
* 100 resources on the server which match this query.
|
* 100 resources on the server which match this query.
|
||||||
*
|
* <p>
|
||||||
* @see #DEFAULT_LOGICAL_BASE_URLS The default value for this setting
|
* The default value is <code>null</code>, which means that there is no limit.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public void setMaximumSearchResultCountInTransaction(int theMaximumSearchResultCountInTransaction) {
|
public void setMaximumSearchResultCountInTransaction(Integer theMaximumSearchResultCountInTransaction) {
|
||||||
myMaximumSearchResultCountInTransaction = theMaximumSearchResultCountInTransaction;
|
myMaximumSearchResultCountInTransaction = theMaximumSearchResultCountInTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,8 +106,6 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
|
||||||
|
|
||||||
Bundle resp = new Bundle();
|
Bundle resp = new Bundle();
|
||||||
resp.setType(BundleTypeEnum.BATCH_RESPONSE);
|
resp.setType(BundleTypeEnum.BATCH_RESPONSE);
|
||||||
OperationOutcome ooResp = new OperationOutcome();
|
|
||||||
resp.addEntry().setResource(ooResp);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others
|
* For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others
|
||||||
|
@ -163,7 +161,6 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
|
||||||
|
|
||||||
long delay = System.currentTimeMillis() - start;
|
long delay = System.currentTimeMillis() - start;
|
||||||
ourLog.info("Batch completed in {}ms", new Object[] { delay });
|
ourLog.info("Batch completed in {}ms", new Object[] { delay });
|
||||||
ooResp.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Batch completed in " + delay + "ms");
|
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,8 +114,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
|
|
||||||
Bundle resp = new Bundle();
|
Bundle resp = new Bundle();
|
||||||
resp.setType(BundleType.BATCHRESPONSE);
|
resp.setType(BundleType.BATCHRESPONSE);
|
||||||
OperationOutcome ooResp = new OperationOutcome();
|
|
||||||
resp.addEntry().setResource(ooResp);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others
|
* For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others
|
||||||
|
@ -171,7 +169,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
|
|
||||||
long delay = System.currentTimeMillis() - start;
|
long delay = System.currentTimeMillis() - start;
|
||||||
ourLog.info("Batch completed in {}ms", new Object[] { delay });
|
ourLog.info("Batch completed in {}ms", new Object[] { delay });
|
||||||
ooResp.addIssue().setSeverity(IssueSeverity.INFORMATION).setDiagnostics("Batch completed in " + delay + "ms");
|
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,6 @@ public class DatabaseBackedPagingProvider extends BasePagingProvider implements
|
||||||
@Override
|
@Override
|
||||||
public synchronized String storeResultList(IBundleProvider theList) {
|
public synchronized String storeResultList(IBundleProvider theList) {
|
||||||
String uuid = theList.getUuid();
|
String uuid = theList.getUuid();
|
||||||
Validate.notNull(uuid);
|
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
||||||
|
|
||||||
DataSource dataSource = ProxyDataSourceBuilder
|
DataSource dataSource = ProxyDataSourceBuilder
|
||||||
.create(retVal)
|
.create(retVal)
|
||||||
.multiline()
|
|
||||||
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||||
.countQuery()
|
.countQuery()
|
||||||
|
@ -70,7 +69,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
||||||
private Properties jpaProperties() {
|
private Properties jpaProperties() {
|
||||||
Properties extraProperties = new Properties();
|
Properties extraProperties = new Properties();
|
||||||
extraProperties.put("hibernate.jdbc.batch_size", "50");
|
extraProperties.put("hibernate.jdbc.batch_size", "50");
|
||||||
extraProperties.put("hibernate.format_sql", "true");
|
extraProperties.put("hibernate.format_sql", "false");
|
||||||
extraProperties.put("hibernate.show_sql", "false");
|
extraProperties.put("hibernate.show_sql", "false");
|
||||||
extraProperties.put("hibernate.hbm2ddl.auto", "update");
|
extraProperties.put("hibernate.hbm2ddl.auto", "update");
|
||||||
extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect");
|
extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect");
|
||||||
|
|
|
@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||||
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||||
|
@ -75,7 +76,6 @@ import ca.uhn.fhir.util.TestUtil;
|
||||||
@ContextConfiguration(classes= {TestDstu2Config.class, ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig.class})
|
@ContextConfiguration(classes= {TestDstu2Config.class, ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig.class})
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ApplicationContext myAppCtx;
|
protected ApplicationContext myAppCtx;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -114,18 +114,18 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myLocationDaoDstu2")
|
@Qualifier("myLocationDaoDstu2")
|
||||||
protected IFhirResourceDao<Location> myLocationDao;
|
protected IFhirResourceDao<Location> myLocationDao;
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("myMediaDaoDstu2")
|
||||||
|
protected IFhirResourceDao<Media> myMediaDao;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myMediaDaoDstu2")
|
@Qualifier("myMedicationAdministrationDaoDstu2")
|
||||||
protected IFhirResourceDao<Media> myMediaDao;
|
protected IFhirResourceDao<MedicationAdministration> myMedicationAdministrationDao;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myMedicationDaoDstu2")
|
@Qualifier("myMedicationDaoDstu2")
|
||||||
protected IFhirResourceDao<Medication> myMedicationDao;
|
protected IFhirResourceDao<Medication> myMedicationDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myMedicationAdministrationDaoDstu2")
|
|
||||||
protected IFhirResourceDao<MedicationAdministration> myMedicationAdministrationDao;
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("myMedicationOrderDaoDstu2")
|
@Qualifier("myMedicationOrderDaoDstu2")
|
||||||
protected IFhirResourceDao<MedicationOrder> myMedicationOrderDao;
|
protected IFhirResourceDao<MedicationOrder> myMedicationOrderDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -135,6 +135,8 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
||||||
@Qualifier("myOrganizationDaoDstu2")
|
@Qualifier("myOrganizationDaoDstu2")
|
||||||
protected IFhirResourceDao<Organization> myOrganizationDao;
|
protected IFhirResourceDao<Organization> myOrganizationDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected DatabaseBackedPagingProvider myPagingProvider;
|
||||||
|
@Autowired
|
||||||
@Qualifier("myPatientDaoDstu2")
|
@Qualifier("myPatientDaoDstu2")
|
||||||
protected IFhirResourceDaoPatient<Patient> myPatientDao;
|
protected IFhirResourceDaoPatient<Patient> myPatientDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -150,8 +152,12 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
||||||
@Qualifier("myResourceProvidersDstu2")
|
@Qualifier("myResourceProvidersDstu2")
|
||||||
protected Object myResourceProviders;
|
protected Object myResourceProviders;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||||
|
@Autowired
|
||||||
protected IFulltextSearchSvc mySearchDao;
|
protected IFulltextSearchSvc mySearchDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
||||||
|
@Autowired
|
||||||
@Qualifier("myStructureDefinitionDaoDstu2")
|
@Qualifier("myStructureDefinitionDaoDstu2")
|
||||||
protected IFhirResourceDao<StructureDefinition> myStructureDefinitionDao;
|
protected IFhirResourceDao<StructureDefinition> myStructureDefinitionDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -171,10 +177,6 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myValueSetDaoDstu2")
|
@Qualifier("myValueSetDaoDstu2")
|
||||||
protected IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> myValueSetDao;
|
protected IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> myValueSetDao;
|
||||||
@Autowired
|
|
||||||
protected ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
|
||||||
@Autowired
|
|
||||||
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void beforeCreateInterceptor() {
|
public void beforeCreateInterceptor() {
|
||||||
|
|
|
@ -86,8 +86,7 @@ import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.search.*;
|
||||||
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
|
|
||||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
||||||
|
@ -108,8 +107,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
||||||
private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3;
|
private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3;
|
||||||
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
||||||
|
|
||||||
// @Autowired
|
// @Autowired
|
||||||
// protected HapiWorkerContext myHapiWorkerContext;
|
// protected HapiWorkerContext myHapiWorkerContext;
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myAllergyIntoleranceDaoDstu3")
|
@Qualifier("myAllergyIntoleranceDaoDstu3")
|
||||||
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;
|
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;
|
||||||
|
@ -124,7 +123,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myBundleDaoDstu3")
|
@Qualifier("myBundleDaoDstu3")
|
||||||
protected IFhirResourceDao<Bundle> myBundleDao;
|
protected IFhirResourceDao<Bundle> myBundleDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myCarePlanDaoDstu3")
|
@Qualifier("myCarePlanDaoDstu3")
|
||||||
protected IFhirResourceDao<CarePlan> myCarePlanDao;
|
protected IFhirResourceDao<CarePlan> myCarePlanDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -195,6 +194,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
||||||
@Qualifier("myOrganizationDaoDstu3")
|
@Qualifier("myOrganizationDaoDstu3")
|
||||||
protected IFhirResourceDao<Organization> myOrganizationDao;
|
protected IFhirResourceDao<Organization> myOrganizationDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected DatabaseBackedPagingProvider myPagingProvider;
|
||||||
|
@Autowired
|
||||||
@Qualifier("myPatientDaoDstu3")
|
@Qualifier("myPatientDaoDstu3")
|
||||||
protected IFhirResourceDaoPatient<Patient> myPatientDao;
|
protected IFhirResourceDaoPatient<Patient> myPatientDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
|
@ -0,0 +1,304 @@
|
||||||
|
package ca.uhn.fhir.jpa.provider;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.contains;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test;
|
||||||
|
import ca.uhn.fhir.jpa.rp.dstu2.*;
|
||||||
|
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
|
import ca.uhn.fhir.model.dstu2.valueset.*;
|
||||||
|
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||||
|
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
|
||||||
|
public class SystemProviderTransactionSearchDstu2Test extends BaseJpaDstu2Test {
|
||||||
|
|
||||||
|
private static RestfulServer myRestServer;
|
||||||
|
private static IGenericClient ourClient;
|
||||||
|
private static FhirContext ourCtx;
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderTransactionSearchDstu2Test.class);
|
||||||
|
private static Server ourServer;
|
||||||
|
private static String ourServerBase;
|
||||||
|
private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
myRestServer.setUseBrowserFriendlyContentTypes(true);
|
||||||
|
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
|
||||||
|
myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor();
|
||||||
|
ourClient.registerInterceptor(mySimpleHeaderInterceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeStartServer() throws Exception {
|
||||||
|
if (myRestServer == null) {
|
||||||
|
PatientResourceProvider patientRp = new PatientResourceProvider();
|
||||||
|
patientRp.setDao(myPatientDao);
|
||||||
|
|
||||||
|
QuestionnaireResourceProviderDstu2 questionnaireRp = new QuestionnaireResourceProviderDstu2();
|
||||||
|
questionnaireRp.setDao(myQuestionnaireDao);
|
||||||
|
|
||||||
|
ObservationResourceProvider observationRp = new ObservationResourceProvider();
|
||||||
|
observationRp.setDao(myObservationDao);
|
||||||
|
|
||||||
|
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
|
||||||
|
organizationRp.setDao(myOrganizationDao);
|
||||||
|
|
||||||
|
RestfulServer restServer = new RestfulServer(ourCtx);
|
||||||
|
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
|
||||||
|
|
||||||
|
restServer.setPlainProviders(mySystemProvider);
|
||||||
|
|
||||||
|
int myPort = RandomServerPortProvider.findFreePort();
|
||||||
|
ourServer = new Server(myPort);
|
||||||
|
|
||||||
|
ServletContextHandler proxyHandler = new ServletContextHandler();
|
||||||
|
proxyHandler.setContextPath("/");
|
||||||
|
|
||||||
|
ourServerBase = "http://localhost:" + myPort + "/fhir/context";
|
||||||
|
|
||||||
|
ServletHolder servletHolder = new ServletHolder();
|
||||||
|
servletHolder.setServlet(restServer);
|
||||||
|
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
|
||||||
|
|
||||||
|
ourCtx = FhirContext.forDstu2();
|
||||||
|
restServer.setFhirContext(ourCtx);
|
||||||
|
|
||||||
|
ourServer.setHandler(proxyHandler);
|
||||||
|
ourServer.start();
|
||||||
|
|
||||||
|
ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000);
|
||||||
|
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
|
||||||
|
myRestServer = restServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
myRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
|
||||||
|
myRestServer.setPagingProvider(myPagingProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<String> create20Patients() {
|
||||||
|
List<String> ids = new ArrayList<String>();
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setGender(AdministrativeGenderEnum.MALE);
|
||||||
|
patient.addIdentifier().setSystem("urn:foo").setValue("A");
|
||||||
|
patient.addName().addFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i+1));
|
||||||
|
String id = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchWithGetHardLimitLargeSynchronous() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleTypeEnum.BATCH);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerbEnum.GET)
|
||||||
|
.setUrl("Patient?_count=5");
|
||||||
|
|
||||||
|
myDaoConfig.setMaximumSearchResultCountInTransaction(100);
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(1, output.getEntry().size());
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
assertEquals(null, respBundle.getLink("next"));
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchWithGetNormalSearch() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleTypeEnum.BATCH);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerbEnum.GET)
|
||||||
|
.setUrl("Patient?_count=5&_sort=name");
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(1, output.getEntry().size());
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
|
||||||
|
String nextPageLink = respBundle.getLink("next").getUrl();
|
||||||
|
output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute();
|
||||||
|
respBundle = output;
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 30 searches in one batch! Whoa!
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testBatchWithManyGets() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleTypeEnum.BATCH);
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerbEnum.GET)
|
||||||
|
.setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(30, output.getEntry().size());
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(i).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
assertThat(respBundle.getLink("next").getUrl(), not(nullValue()));
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithGetHardLimitLargeSynchronous() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleTypeEnum.TRANSACTION);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerbEnum.GET)
|
||||||
|
.setUrl("Patient?_count=5");
|
||||||
|
|
||||||
|
myDaoConfig.setMaximumSearchResultCountInTransaction(100);
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(1, output.getEntry().size());
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
assertEquals(null, respBundle.getLink("next"));
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithGetNormalSearch() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleTypeEnum.TRANSACTION);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerbEnum.GET)
|
||||||
|
.setUrl("Patient?_count=5&_sort=name");
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(1, output.getEntry().size());
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
|
||||||
|
String nextPageLink = respBundle.getLink("next").getUrl();
|
||||||
|
output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute();
|
||||||
|
respBundle = output;
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 30 searches in one Transaction! Whoa!
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithManyGets() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleTypeEnum.TRANSACTION);
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerbEnum.GET)
|
||||||
|
.setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(30, output.getEntry().size());
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(i).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
assertThat(respBundle.getLink("next").getUrl(), not(nullValue()));
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> toIds(Bundle theRespBundle) {
|
||||||
|
ArrayList<String> retVal = new ArrayList<String>();
|
||||||
|
for (Entry next : theRespBundle.getEntry()) {
|
||||||
|
retVal.add(next.getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClassClearContext() throws Exception {
|
||||||
|
ourServer.stop();
|
||||||
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -58,6 +58,7 @@ import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
|
||||||
import ca.uhn.fhir.jpa.rp.dstu3.ObservationResourceProvider;
|
import ca.uhn.fhir.jpa.rp.dstu3.ObservationResourceProvider;
|
||||||
import ca.uhn.fhir.jpa.rp.dstu3.OrganizationResourceProvider;
|
import ca.uhn.fhir.jpa.rp.dstu3.OrganizationResourceProvider;
|
||||||
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
|
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
|
||||||
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
||||||
|
@ -202,7 +203,6 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test {
|
||||||
organizationRp.setDao(myOrganizationDao);
|
organizationRp.setDao(myOrganizationDao);
|
||||||
|
|
||||||
RestfulServer restServer = new RestfulServer(ourCtx);
|
RestfulServer restServer = new RestfulServer(ourCtx);
|
||||||
restServer.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(10));
|
|
||||||
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
|
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
|
||||||
|
|
||||||
restServer.setPlainProviders(mySystemProvider);
|
restServer.setPlainProviders(mySystemProvider);
|
||||||
|
@ -237,6 +237,7 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
myRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
|
myRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
|
||||||
|
myRestServer.setPagingProvider(myPagingProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|
|
@ -0,0 +1,358 @@
|
||||||
|
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.contains;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
|
||||||
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
|
import org.hl7.fhir.dstu3.model.Bundle.*;
|
||||||
|
import org.hl7.fhir.dstu3.model.DecimalType;
|
||||||
|
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
|
||||||
|
import org.hl7.fhir.dstu3.model.IdType;
|
||||||
|
import org.hl7.fhir.dstu3.model.Observation;
|
||||||
|
import org.hl7.fhir.dstu3.model.OperationDefinition;
|
||||||
|
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||||
|
import org.hl7.fhir.dstu3.model.Organization;
|
||||||
|
import org.hl7.fhir.dstu3.model.Parameters;
|
||||||
|
import org.hl7.fhir.dstu3.model.Patient;
|
||||||
|
import org.hl7.fhir.dstu3.model.StringType;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoMethodOutcome;
|
||||||
|
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
|
||||||
|
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
|
||||||
|
import ca.uhn.fhir.jpa.rp.dstu3.ObservationResourceProvider;
|
||||||
|
import ca.uhn.fhir.jpa.rp.dstu3.OrganizationResourceProvider;
|
||||||
|
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
|
||||||
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
|
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||||
|
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||||
|
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||||
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||||
|
|
||||||
|
public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test {
|
||||||
|
|
||||||
|
private static RestfulServer myRestServer;
|
||||||
|
private static IGenericClient ourClient;
|
||||||
|
private static FhirContext ourCtx;
|
||||||
|
private static CloseableHttpClient ourHttpClient;
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderTransactionSearchDstu3Test.class);
|
||||||
|
private static Server ourServer;
|
||||||
|
private static String ourServerBase;
|
||||||
|
private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
myRestServer.setUseBrowserFriendlyContentTypes(true);
|
||||||
|
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
|
||||||
|
myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor();
|
||||||
|
ourClient.registerInterceptor(mySimpleHeaderInterceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeStartServer() throws Exception {
|
||||||
|
if (myRestServer == null) {
|
||||||
|
PatientResourceProvider patientRp = new PatientResourceProvider();
|
||||||
|
patientRp.setDao(myPatientDao);
|
||||||
|
|
||||||
|
QuestionnaireResourceProviderDstu3 questionnaireRp = new QuestionnaireResourceProviderDstu3();
|
||||||
|
questionnaireRp.setDao(myQuestionnaireDao);
|
||||||
|
|
||||||
|
ObservationResourceProvider observationRp = new ObservationResourceProvider();
|
||||||
|
observationRp.setDao(myObservationDao);
|
||||||
|
|
||||||
|
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
|
||||||
|
organizationRp.setDao(myOrganizationDao);
|
||||||
|
|
||||||
|
RestfulServer restServer = new RestfulServer(ourCtx);
|
||||||
|
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
|
||||||
|
|
||||||
|
restServer.setPlainProviders(mySystemProvider);
|
||||||
|
|
||||||
|
int myPort = RandomServerPortProvider.findFreePort();
|
||||||
|
ourServer = new Server(myPort);
|
||||||
|
|
||||||
|
ServletContextHandler proxyHandler = new ServletContextHandler();
|
||||||
|
proxyHandler.setContextPath("/");
|
||||||
|
|
||||||
|
ourServerBase = "http://localhost:" + myPort + "/fhir/context";
|
||||||
|
|
||||||
|
ServletHolder servletHolder = new ServletHolder();
|
||||||
|
servletHolder.setServlet(restServer);
|
||||||
|
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
|
||||||
|
|
||||||
|
ourCtx = FhirContext.forDstu3();
|
||||||
|
restServer.setFhirContext(ourCtx);
|
||||||
|
|
||||||
|
ourServer.setHandler(proxyHandler);
|
||||||
|
ourServer.start();
|
||||||
|
|
||||||
|
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||||
|
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||||
|
builder.setConnectionManager(connectionManager);
|
||||||
|
ourHttpClient = builder.build();
|
||||||
|
|
||||||
|
ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000);
|
||||||
|
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
|
||||||
|
ourClient.setLogRequestAndResponse(true);
|
||||||
|
myRestServer = restServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
myRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
|
||||||
|
myRestServer.setPagingProvider(myPagingProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<String> create20Patients() {
|
||||||
|
List<String> ids = new ArrayList<String>();
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setGender(AdministrativeGender.MALE);
|
||||||
|
patient.addIdentifier().setSystem("urn:foo").setValue("A");
|
||||||
|
patient.addName().setFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i+1));
|
||||||
|
String id = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchWithGetHardLimitLargeSynchronous() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleType.BATCH);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient?_count=5");
|
||||||
|
|
||||||
|
myDaoConfig.setMaximumSearchResultCountInTransaction(100);
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(1, output.getEntry().size());
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
assertEquals(null, respBundle.getLink("next"));
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchWithGetNormalSearch() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleType.BATCH);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient?_count=5&_sort=name");
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(1, output.getEntry().size());
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
|
||||||
|
String nextPageLink = respBundle.getLink("next").getUrl();
|
||||||
|
output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute();
|
||||||
|
respBundle = output;
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 30 searches in one batch! Whoa!
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testBatchWithManyGets() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleType.BATCH);
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(30, output.getEntry().size());
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(i).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
assertThat(respBundle.getLink("next").getUrl(), not(nullValue()));
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithGetHardLimitLargeSynchronous() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleType.TRANSACTION);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient?_count=5");
|
||||||
|
|
||||||
|
myDaoConfig.setMaximumSearchResultCountInTransaction(100);
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(1, output.getEntry().size());
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
assertEquals(null, respBundle.getLink("next"));
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithGetNormalSearch() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleType.TRANSACTION);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient?_count=5&_sort=name");
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(1, output.getEntry().size());
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
|
||||||
|
String nextPageLink = respBundle.getLink("next").getUrl();
|
||||||
|
output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute();
|
||||||
|
respBundle = output;
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 30 searches in one Transaction! Whoa!
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithManyGets() throws Exception {
|
||||||
|
List<String> ids = create20Patients();
|
||||||
|
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleType.TRANSACTION);
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bundle output = ourClient.transaction().withBundle(input).execute();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
|
assertEquals(30, output.getEntry().size());
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
Bundle respBundle = (Bundle) output.getEntry().get(i).getResource();
|
||||||
|
assertEquals(5, respBundle.getEntry().size());
|
||||||
|
assertThat(respBundle.getLink("next").getUrl(), not(nullValue()));
|
||||||
|
List<String> actualIds = toIds(respBundle);
|
||||||
|
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> toIds(Bundle theRespBundle) {
|
||||||
|
ArrayList<String> retVal = new ArrayList<String>();
|
||||||
|
for (BundleEntryComponent next : theRespBundle.getEntry()) {
|
||||||
|
retVal.add(next.getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClassClearContext() throws Exception {
|
||||||
|
ourServer.stop();
|
||||||
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package ca.uhn.fhir.rest.server.provider.dstu2;
|
package ca.uhn.fhir.rest.server.provider.dstu2;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
|
* HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
|
||||||
|
@ -10,7 +11,7 @@ package ca.uhn.fhir.rest.server.provider.dstu2;
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -62,6 +63,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||||
|
|
||||||
public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
|
public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Dstu2BundleFactory.class);
|
||||||
|
|
||||||
private Bundle myBundle;
|
private Bundle myBundle;
|
||||||
private FhirContext myContext;
|
private FhirContext myContext;
|
||||||
|
@ -340,7 +342,9 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
|
||||||
} else {
|
} else {
|
||||||
if (numTotalResults == null || numTotalResults > numToReturn) {
|
if (numTotalResults == null || numTotalResults > numToReturn) {
|
||||||
searchId = pagingProvider.storeResultList(theResult);
|
searchId = pagingProvider.storeResultList(theResult);
|
||||||
Validate.notNull(searchId, "Paging provider returned null searchId");
|
if (isBlank(searchId)) {
|
||||||
|
ourLog.info("Found {} results but paging provider did not provide an ID to use for paging", numTotalResults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.hl7.fhir.dstu3.hapi.rest.server;
|
package org.hl7.fhir.dstu3.hapi.rest.server;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
|
* HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
|
||||||
|
@ -44,7 +45,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||||
|
|
||||||
public class Dstu3BundleFactory implements IVersionSpecificBundleFactory {
|
public class Dstu3BundleFactory implements IVersionSpecificBundleFactory {
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Dstu3BundleFactory.class);
|
||||||
private Bundle myBundle;
|
private Bundle myBundle;
|
||||||
private FhirContext myContext;
|
private FhirContext myContext;
|
||||||
private String myBase;
|
private String myBase;
|
||||||
|
@ -336,7 +337,9 @@ public class Dstu3BundleFactory implements IVersionSpecificBundleFactory {
|
||||||
} else {
|
} else {
|
||||||
if (numTotalResults == null || numTotalResults > numToReturn) {
|
if (numTotalResults == null || numTotalResults > numToReturn) {
|
||||||
searchId = pagingProvider.storeResultList(theResult);
|
searchId = pagingProvider.storeResultList(theResult);
|
||||||
Validate.notNull(searchId, "Paging provider returned null searchId");
|
if (isBlank(searchId)) {
|
||||||
|
ourLog.info("Found {} results but paging provider did not provide an ID to use for paging", numTotalResults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,23 @@
|
||||||
(tags, profiles, and security labels) on an individual resource. The default
|
(tags, profiles, and security labels) on an individual resource. The default
|
||||||
is 1000.
|
is 1000.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
When executing a search (HTTP GET) as a nested operation in in a transaction or
|
||||||
|
batch operation, the search now returns a normal page of results with a link to
|
||||||
|
the next page, like any other search would. Previously the search would return
|
||||||
|
a small number of results with no paging performed, so this change brings transaction
|
||||||
|
and batch processing in line with other types of search.
|
||||||
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
JPA server no longer returns an OperationOutcome resource as the first resource
|
||||||
|
in the Bundle for a response to a batch operation. This behaviour was previously
|
||||||
|
present, but was not specified in the FHIR specification so it caused confusion and
|
||||||
|
was inconsistent with behaviour in other servers.
|
||||||
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
Fix a regression in HAPI FHIR 2.5 JPA server where executing a search in a
|
||||||
|
transaction or batch operation caused an exception.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="2.5" date="2017-06-08">
|
<release version="2.5" date="2017-06-08">
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
|
Loading…
Reference in New Issue