Update resource provenance indexing strategy (#4883)

* Add migration

* Update indexes

* Update hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java

Co-authored-by: michaelabuckley <michaelabuckley@gmail.com>

* Address review comments

* Test fixes

* Test fixes

* Test fix

---------

Co-authored-by: michaelabuckley <michaelabuckley@gmail.com>
This commit is contained in:
James Agnew 2023-05-18 13:23:57 -04:00 committed by GitHub
parent 54cec79a65
commit 1c66e57465
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 312 additions and 203 deletions

View File

@ -1 +1,7 @@
Users of the `Resource.meta.source` field, as well as users of the `_source` parameter should perform a global $reindex after upgrading to this version of HAPI FHIR with the following parameters:
```url
[base]/$reindex?reindexSearchParameters=false&optimizeStorage=ALL_VERSIONS
```
The previous mechanism for storing and indexing these parameters is inefficient and will be replaced in a future release of HAPI FHIR. Performing this reindex operation ensures that existing data will continue to be searchable.

View File

@ -1336,10 +1336,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
provenance.setResourceTable(theEntity);
provenance.setPartitionId(theEntity.getPartitionId());
if (haveRequestId) {
provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH));
String persistedRequestId = left(requestId, Constants.REQUEST_ID_LENGTH);
provenance.setRequestId(persistedRequestId);
historyEntry.setRequestId(persistedRequestId);
}
if (haveSource) {
provenance.setSourceUri(source);
String persistedSource = left(source, ResourceHistoryTable.SOURCE_URI_LENGTH);
provenance.setSourceUri(persistedSource);
historyEntry.setSourceUri(persistedSource);
}
if (theResource != null) {
MetaUtil.populateResourceSource(myFhirContext, shouldStoreSource ? source : null, shouldStoreRequestId ? requestId : null , theResource);

View File

@ -1439,8 +1439,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
reindexOptimizeStorageHistoryEntity(entity, historyEntity);
if (theOptimizeStorageMode == ReindexParameters.OptimizeStorageModeEnum.ALL_VERSIONS) {
int pageSize = 100;
for (int page = 0; ((long) page * pageSize) < entity.getVersion(); page++) {
Slice<ResourceHistoryTable> historyEntities = myResourceHistoryTableDao.findForResourceIdAndReturnEntities(PageRequest.of(page, pageSize), entity.getId(), historyEntity.getVersion());
for (int page = 0; ((long)page * pageSize) < entity.getVersion(); page++) {
Slice<ResourceHistoryTable> historyEntities = myResourceHistoryTableDao.findForResourceIdAndReturnEntitiesAndFetchProvenance(PageRequest.of(page, pageSize), entity.getId(), historyEntity.getVersion());
for (ResourceHistoryTable next : historyEntities) {
reindexOptimizeStorageHistoryEntity(entity, next);
}
@ -1450,16 +1450,30 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
private void reindexOptimizeStorageHistoryEntity(ResourceTable entity, ResourceHistoryTable historyEntity) {
boolean changed = false;
if (historyEntity.getEncoding() == ResourceEncodingEnum.JSONC || historyEntity.getEncoding() == ResourceEncodingEnum.JSON) {
byte[] resourceBytes = historyEntity.getResource();
if (resourceBytes != null) {
String resourceText = decodeResource(resourceBytes, historyEntity.getEncoding());
if (myStorageSettings.getInlineResourceTextBelowSize() > 0 && resourceText.length() < myStorageSettings.getInlineResourceTextBelowSize()) {
ourLog.debug("Storing text of resource {} version {} as inline VARCHAR", entity.getResourceId(), historyEntity.getVersion());
myResourceHistoryTableDao.setResourceTextVcForVersion(historyEntity.getId(), resourceText);
historyEntity.setResourceTextVc(resourceText);
historyEntity.setResource(null);
historyEntity.setEncoding(ResourceEncodingEnum.JSON);
changed = true;
}
}
}
if (isBlank(historyEntity.getSourceUri()) && isBlank(historyEntity.getRequestId())) {
if (historyEntity.getProvenance() != null) {
historyEntity.setSourceUri(historyEntity.getProvenance().getSourceUri());
historyEntity.setRequestId(historyEntity.getProvenance().getRequestId());
changed = true;
}
}
if (changed) {
myResourceHistoryTableDao.save(historyEntity);
}
}
private BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest, RequestPartitionId requestPartitionId) {

View File

@ -44,8 +44,8 @@ public interface IResourceHistoryTableDao extends JpaRepository<ResourceHistoryT
@Query("SELECT t.myId FROM ResourceHistoryTable t WHERE t.myResourceId = :resId AND t.myResourceVersion != :dontWantVersion")
Slice<Long> findForResourceId(Pageable thePage, @Param("resId") Long theId, @Param("dontWantVersion") Long theDontWantVersion);
@Query("SELECT t FROM ResourceHistoryTable t WHERE t.myResourceId = :resId AND t.myResourceVersion != :dontWantVersion")
Slice<ResourceHistoryTable> findForResourceIdAndReturnEntities(Pageable thePage, @Param("resId") Long theId, @Param("dontWantVersion") Long theDontWantVersion);
@Query("SELECT t FROM ResourceHistoryTable t LEFT OUTER JOIN FETCH t.myProvenance WHERE t.myResourceId = :resId AND t.myResourceVersion != :dontWantVersion")
Slice<ResourceHistoryTable> findForResourceIdAndReturnEntitiesAndFetchProvenance(Pageable thePage, @Param("resId") Long theId, @Param("dontWantVersion") Long theDontWantVersion);
@Query("" +
"SELECT v.myId FROM ResourceHistoryTable v " +
@ -67,13 +67,6 @@ public interface IResourceHistoryTableDao extends JpaRepository<ResourceHistoryT
"WHERE v.myResourceVersion != t.myVersion")
Slice<Long> findIdsOfPreviousVersionsOfResources(Pageable thePage);
/**
* Sets the inline text and clears the LOB copy of the text
*/
@Modifying
@Query("UPDATE ResourceHistoryTable as t SET t.myResource = null, t.myResourceTextVc = :text WHERE t.myId = :pid")
void setResourceTextVcForVersion(@Param("pid") Long id, @Param("text") String resourceText);
@Modifying
@Query("UPDATE ResourceHistoryTable r SET r.myResourceVersion = :newVersion WHERE r.myResourceId = :id AND r.myResourceVersion = :oldVersion")
void updateVersion(@Param("id") long theId, @Param("oldVersion") long theOldVersion, @Param("newVersion") long theNewVersion);
@ -81,4 +74,5 @@ public interface IResourceHistoryTableDao extends JpaRepository<ResourceHistoryT
@Modifying
@Query("DELETE FROM ResourceHistoryTable t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId);
}

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.Constants;
@ -43,6 +44,7 @@ import javax.persistence.TemporalType;
import java.io.Serializable;
import java.util.Date;
@SuppressWarnings("SqlDialectInspection")
@Entity
@Immutable
@Subselect("SELECT h.pid as pid, " +
@ -83,7 +85,7 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
private Long myResourceVersion;
@Column(name = "PROV_REQUEST_ID", length = Constants.REQUEST_ID_LENGTH)
private String myProvenanceRequestId;
@Column(name = "PROV_SOURCE_URI", length = ResourceHistoryProvenanceEntity.SOURCE_URI_LENGTH)
@Column(name = "PROV_SOURCE_URI", length = ResourceHistoryTable.SOURCE_URI_LENGTH)
private String myProvenanceSourceUri;
@Column(name = "HAS_TAGS")
private boolean myHasTags;

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
import ca.uhn.fhir.jpa.migrate.tasks.api.Builder;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
@ -120,8 +121,24 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.online(false)
.withColumns("TAG_TYPE", "TAG_CODE", "TAG_SYSTEM", "TAG_ID", "TAG_VERSION", "TAG_USER_SELECTED");
version.onTable("HFJ_RES_VER_PROV")
.addIndex("20230510.1", "IDX_RESVERPROV_RES_VER_PID")
.unique(false)
.withColumns("RES_VER_PID")
.failureAllowed();
version.onTable("HFJ_RES_VER_PROV")
.addIndex("20230510.2", "IDX_RESVERPROV_RES_PID")
.unique(false)
.withColumns("RES_PID");
version.onTable(ResourceHistoryTable.HFJ_RES_VER)
.addColumn("20230510.4", "SOURCE_URI")
.nullable()
.type(ColumnTypeEnum.STRING, 100);
version.onTable(ResourceHistoryTable.HFJ_RES_VER)
.addColumn("20230510.5", "REQUEST_ID")
.nullable()
.type(ColumnTypeEnum.STRING, 16);
}
protected void init660() {

View File

@ -949,12 +949,6 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
}
}
private Consumer<IBaseResource>[] asArray(Consumer<IBaseResource> theIBaseResourceConsumer) {
@SuppressWarnings("unchecked")
Consumer<IBaseResource>[] array = (Consumer<IBaseResource>[]) new Consumer[]{theIBaseResourceConsumer};
return array;
}
private List<String> getResultIds(IBundleProvider theResult) {
return theResult.getAllResources().stream().map(r -> r.getIdElement().getIdPart()).collect(Collectors.toList());
}
@ -1005,7 +999,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
@Test
void secondWordFound() {
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Cloudy, yellow"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Cloudy, yellow"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?value-string:text=yellow");
assertThat(resourceIds, hasItem(id1));
@ -1016,9 +1010,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
// smit - matches "smit" and "smith"
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "John Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "John Smith"))).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Carl Smit"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Carl Smit"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?value-string:text=smit");
assertThat(resourceIds, hasItems(id1, id2));
@ -1030,9 +1024,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
// smit* - matches "smit" and "smith"
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "John Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "John Smith"))).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Carl Smit"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Carl Smit"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?_elements=valueString&value-string:text=smit*");
assertThat(resourceIds, hasItems(id1, id2));
@ -1044,9 +1038,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
// "smit"- matches "smit", but not "smith"
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "John Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "John Smith"))).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Carl Smit"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Carl Smit"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?value-string:text=\"smit\"");
assertThat(resourceIds, contains(id2));
@ -1056,9 +1050,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
@Test
void stringTokensAreAnded() {
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "John Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "John Smith"))).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Carl Smit"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Carl Smit"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?value-string:text=car%20smit");
assertThat(resourceIds, hasItems(id2));
@ -1072,9 +1066,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
// | Fhir Query String | Executed Query | Matches | No Match
// | Smit | Smit* | John Smith | John Smi
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "John Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "John Smith"))).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "John Smi"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "John Smi"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?value-string:text=Smit");
assertThat(resourceIds, hasItems(id1));
@ -1085,9 +1079,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
// | Fhir Query String | Executed Query | Matches | No Match | Note
// | Jo Smit | Jo* Smit* | John Smith | John Frank | Multiple bare terms are `AND`
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "John Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "John Smith"))).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "John Frank"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "John Frank"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?value-string:text=Jo%20Smit");
assertThat(resourceIds, hasItems(id1));
@ -1098,9 +1092,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
// | Fhir Query String | Executed Query | Matches | No Match | Note
// | frank &vert; john | frank &vert; john | Frank Smith | Franklin Smith | SQS characters disable prefix wildcard
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Frank Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Frank Smith"))).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Franklin Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Franklin Smith"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?value-string:text=frank|john");
assertThat(resourceIds, hasItems(id1));
@ -1111,9 +1105,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
// | Fhir Query String | Executed Query | Matches | No Match | Note
// | 'frank' | 'frank' | Frank Smith | Franklin Smith | Quoted terms are exact match
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Frank Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Frank Smith"))).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "Franklin Smith"))).getIdPart();
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "Franklin Smith"))).getIdPart();
List<String> resourceIds = myTestDaoSearch.searchForIds("/Observation?value-string:text='frank'");
assertThat(resourceIds, hasItems(id1));
@ -1805,10 +1799,10 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
@Test
public void byValueString() {
String id1 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "a-string-value-1")
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "a-string-value-1")
)).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "a-string-value-2")
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "a-string-value-2")
)).getIdPart();
myCaptureQueriesListener.clear();
@ -1950,7 +1944,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
myTestDataBuilder.withObservationCode("http://example.com/", "the-code-1")
)).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withPrimitiveAttribute("valueString", "a-string-value-2")
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "a-string-value-2")
)).getIdPart();
myCaptureQueriesListener.clear();
@ -2065,13 +2059,13 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
myTestDataBuilder.withObservationCode("http://example.com/", "the-code-1"),
myTestDataBuilder.withEffectiveDate("2017-01-20T03:21:47"),
myTestDataBuilder.withTag("http://example.org", "aTag"),
myTestDataBuilder.withPrimitiveAttribute("valueString", "a-string-value-1")
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "a-string-value-1")
)).getIdPart();
String id2 = myTestDataBuilder.createObservation(List.of(
myTestDataBuilder.withObservationCode("http://example.com/", "the-code-2"),
myTestDataBuilder.withEffectiveDate("2017-01-24T03:21:47"),
myTestDataBuilder.withTag("http://example.org", "aTag"),
myTestDataBuilder.withPrimitiveAttribute("valueString", "a-string-value-2")
myTestDataBuilder.withResourcePrimitiveAttribute("valueString", "a-string-value-2")
)).getIdPart();
myCaptureQueriesListener.clear();

View File

@ -34,16 +34,20 @@ import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import static ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable.SOURCE_URI_LENGTH;
@Table(name = "HFJ_RES_VER_PROV", indexes = {
@Index(name = "IDX_RESVERPROV_SOURCEURI", columnList = "SOURCE_URI"),
@Index(name = "IDX_RESVERPROV_REQUESTID", columnList = "REQUEST_ID"),
//@Index(name = "IDX_RESVERPROV_RESID", columnList = "RES_PID")
@Index(name = "IDX_RESVERPROV_RES_PID", columnList = "RES_PID"),
@Index(name = "IDX_RESVERPROV_RES_VER_PID", columnList = "RES_VER_PID")
}, uniqueConstraints = {
})
@Entity
public class ResourceHistoryProvenanceEntity extends BasePartitionable {
public static final int SOURCE_URI_LENGTH = 100;
@Id
@Column(name = "RES_VER_PID")

View File

@ -25,28 +25,9 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.OptimisticLock;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
@ -61,6 +42,7 @@ import java.util.Collection;
})
public class ResourceHistoryTable extends BaseHasResource implements Serializable {
public static final String IDX_RESVER_ID_VER = "IDX_RESVER_ID_VER";
public static final int SOURCE_URI_LENGTH = 100;
/**
* @see ResourceEncodingEnum
*/
@ -94,13 +76,18 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
@org.hibernate.annotations.Type(type = JpaConstants.ORG_HIBERNATE_TYPE_TEXT_TYPE)
@OptimisticLock(excluded = true)
private String myResourceTextVc;
@Column(name = "RES_ENCODING", nullable = false, length = ENCODING_COL_LENGTH)
@Enumerated(EnumType.STRING)
@OptimisticLock(excluded = true)
private ResourceEncodingEnum myEncoding;
@OneToOne(mappedBy = "myResourceHistoryTable", cascade = {CascadeType.REMOVE})
private ResourceHistoryProvenanceEntity myProvenance;
// TODO: This was added in 6.8.0 - In the future we should drop ResourceHistoryProvenanceEntity
@Column(name = "SOURCE_URI", length = SOURCE_URI_LENGTH, nullable = true)
private String mySourceUri;
// TODO: This was added in 6.8.0 - In the future we should drop ResourceHistoryProvenanceEntity
@Column(name = "REQUEST_ID", length = Constants.REQUEST_ID_LENGTH, nullable = true)
private String myRequestId;
/**
* Constructor
@ -109,6 +96,22 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
super();
}
public String getSourceUri() {
return mySourceUri;
}
public void setSourceUri(String theSourceUri) {
mySourceUri = theSourceUri;
}
public String getRequestId() {
return myRequestId;
}
public void setRequestId(String theRequestId) {
myRequestId = theRequestId;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
@ -215,6 +218,10 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
return myResourceVersion;
}
public void setVersion(long theVersion) {
myResourceVersion = theVersion;
}
@Override
public boolean isDeleted() {
return getDeleted() != null;
@ -225,10 +232,6 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
setDeleted(null);
}
public void setVersion(long theVersion) {
myResourceVersion = theVersion;
}
@Override
public JpaPid getPersistentId() {
return JpaPid.fromId(myResourceId);

View File

@ -39,7 +39,7 @@ public class ConsumeFilesStepR4Test extends BasePartitioningR4Test {
@BeforeEach
@Override
public void before() throws ServletException {
public void before() throws Exception {
super.before();
myPartitionSettings.setPartitioningEnabled(false);
myStorageSettings.setInlineResourceTextBelowSize(10000);

View File

@ -23,13 +23,11 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletException;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -75,8 +73,10 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
myStorageSettings.setMatchUrlCacheEnabled(new JpaStorageSettings().getMatchUrlCache());
}
@Override
@BeforeEach
public void before() throws ServletException {
public void before() throws Exception {
super.before();
myPartitionSettings.setPartitioningEnabled(true);
myPartitionSettings.setIncludePartitionInSearchHashes(new PartitionSettings().isIncludePartitionInSearchHashes());
@ -183,7 +183,7 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
}
protected Consumer<IBaseResource> withPartition(Integer thePartitionId) {
protected ICreationArgument withPartition(Integer thePartitionId) {
return t -> {
if (thePartitionId != null) {
addCreatePartition(thePartitionId, null);

View File

@ -953,14 +953,14 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
@ParameterizedTest
@CsvSource({
// NoOp OptimisticLock OptimizeMode ExpectedSelect ExpectedUpdate
" false, false, CURRENT_VERSION, 2, 10",
" false, false, CURRENT_VERSION, 2, 1",
" true, false, CURRENT_VERSION, 2, 0",
" false, true, CURRENT_VERSION, 12, 10",
" false, true, CURRENT_VERSION, 12, 1",
" true, true, CURRENT_VERSION, 12, 0",
" false, false, ALL_VERSIONS, 22, 20",
" true, false, ALL_VERSIONS, 22, 0",
" false, true, ALL_VERSIONS, 32, 20",
" true, true, ALL_VERSIONS, 32, 0",
" false, false, ALL_VERSIONS, 12, 10",
" true, false, ALL_VERSIONS, 12, 0",
" false, true, ALL_VERSIONS, 22, 10",
" true, true, ALL_VERSIONS, 22, 0",
})
public void testReindexJob_OptimizeStorage(boolean theNoOp, boolean theOptimisticLock, ReindexParameters.OptimizeStorageModeEnum theOptimizeStorageModeEnum, int theExpectedSelectCount, int theExpectedUpdateCount) {
// Setup
@ -998,7 +998,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
RunOutcome outcome = myReindexStep.doReindex(data, mock(IJobDataSink.class), "123", "456", params);
// validate
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(theExpectedSelectCount, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(theExpectedUpdateCount, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());

View File

@ -12,7 +12,7 @@ import ca.uhn.fhir.jpa.test.config.TestHSearchAddInConfig;
import ca.uhn.fhir.jpa.test.config.TestR4Config;
import ca.uhn.fhir.storage.test.BaseDateSearchDaoTests;
import ca.uhn.fhir.storage.test.DaoTestDataBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.test.utilities.ITestDataBuilder.ICreationArgument;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Observation;
import org.junit.jupiter.api.Disabled;
@ -30,7 +30,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.PlatformTransactionManager;
import java.util.List;
import java.util.function.Consumer;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
@ -194,7 +193,7 @@ public class FhirResourceDaoR4StandardQueriesNoFTTest extends BaseJpaTest {
}
@SafeVarargs
private IIdType withObservation(Consumer<IBaseResource>... theBuilder) {
private IIdType withObservation(ICreationArgument... theBuilder) {
myObservationId = myDataBuilder.createObservation(theBuilder);
return myObservationId;
}
@ -270,7 +269,7 @@ public class FhirResourceDaoR4StandardQueriesNoFTTest extends BaseJpaTest {
IIdType myResourceId;
private IIdType withRiskAssessmentWithProbabilty(double theValue) {
myResourceId = myDataBuilder.createResource("RiskAssessment", myDataBuilder.withPrimitiveAttribute("prediction.probabilityDecimal", theValue));
myResourceId = myDataBuilder.createResource("RiskAssessment", myDataBuilder.withResourcePrimitiveAttribute("prediction.probabilityDecimal", theValue));
return myResourceId;
}

View File

@ -16,7 +16,6 @@ import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
@ -30,7 +29,7 @@ public class PartitioningNonNullDefaultPartitionR4Test extends BasePartitioningR
@BeforeEach
@Override
public void before() throws ServletException {
public void before() throws Exception {
super.before();
myPartitionSettings.setDefaultPartitionId(1);

View File

@ -201,7 +201,7 @@ public class ThreadSafeResourceDeleterSvcTest extends BaseJpaR4Test {
return myOrganizationDao.search(map).size();
}
private IIdType createPatientWithVersion(Consumer<IBaseResource> theWithId) {
private IIdType createPatientWithVersion(ICreationArgument theWithId) {
Patient patient = new Patient();
patient.addName(new HumanName().setFamily("FAMILY"));
theWithId.accept(patient);

View File

@ -29,11 +29,13 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.persistence.Query;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.blankOrNullString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -44,8 +46,6 @@ import static org.junit.jupiter.api.Assertions.fail;
public class ReindexJobTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReindexJobTest.class);
@Autowired
private IJobCoordinator myJobCoordinator;
@ -63,6 +63,8 @@ public class ReindexJobTest extends BaseJpaR4Test {
public void after() {
myInterceptorRegistry.unregisterAllAnonymousInterceptors();
myStorageSettings.setInlineResourceTextBelowSize(new JpaStorageSettings().getInlineResourceTextBelowSize());
myStorageSettings.setStoreMetaSourceInformation(new JpaStorageSettings().getStoreMetaSourceInformation());
myStorageSettings.setPreserveRequestIdInResourceBody(new JpaStorageSettings().isPreserveRequestIdInResourceBody());
}
@Test
@ -73,7 +75,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
Patient p = new Patient();
p.setId(patientId.toUnqualifiedVersionless());
p.setActive(true);
p.addIdentifier().setValue("" + i);
p.addIdentifier().setValue(String.valueOf(i));
myPatientDao.update(p, mySrd);
}
for (int i = 0; i < 9; i++) {
@ -127,7 +129,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
Patient p = new Patient();
p.setId(patientId.toUnqualifiedVersionless());
p.setActive(true);
p.addIdentifier().setValue("" + i);
p.addIdentifier().setValue(String.valueOf(i));
myPatientDao.update(p, mySrd);
}
for (int i = 0; i < 9; i++) {
@ -168,6 +170,66 @@ public class ReindexJobTest extends BaseJpaR4Test {
}
@Test
public void testOptimizeStorage_AllVersions_CopyProvenanceEntityData() {
// Setup
myStorageSettings.setStoreMetaSourceInformation(JpaStorageSettings.StoreMetaSourceInformationEnum.SOURCE_URI_AND_REQUEST_ID);
myStorageSettings.setPreserveRequestIdInResourceBody(true);
for (int i = 0; i < 10; i++) {
Patient p = new Patient();
p.setId("PATIENT" + i);
p.getMeta().setSource("http://foo#bar");
p.addIdentifier().setValue(String.valueOf(i));
myPatientDao.update(p, mySrd);
p.addIdentifier().setSystem("http://blah");
myPatientDao.update(p, mySrd);
}
runInTransaction(()->{
assertEquals(20, myResourceHistoryTableDao.count());
assertEquals(20, myResourceHistoryProvenanceDao.count());
Query query = myEntityManager.createQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " p SET p.mySourceUri = NULL, p.myRequestId = NULL");
assertEquals(20, query.executeUpdate());
});
runInTransaction(()-> {
for (var next : myResourceHistoryProvenanceDao.findAll()) {
assertEquals("bar", next.getRequestId());
assertEquals("http://foo", next.getSourceUri());
}
for (var next : myResourceHistoryTableDao.findAll()) {
assertThat(next.getRequestId(), blankOrNullString());
assertThat(next.getSourceUri(), blankOrNullString());
}
});
// execute
JobInstanceStartRequest startRequest = new JobInstanceStartRequest();
startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX);
startRequest.setParameters(
new ReindexJobParameters()
.setOptimizeStorage(ReindexParameters.OptimizeStorageModeEnum.ALL_VERSIONS)
.setReindexSearchParameters(ReindexParameters.ReindexSearchParametersEnum.NONE)
);
Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(startRequest);
myBatch2JobHelper.awaitJobCompletion(startResponse);
// validate
runInTransaction(()-> {
for (var next : myResourceHistoryProvenanceDao.findAll()) {
assertEquals("bar", next.getRequestId());
assertEquals("http://foo", next.getSourceUri());
}
for (var next : myResourceHistoryTableDao.findAll()) {
assertEquals("bar", next.getRequestId());
assertEquals("http://foo", next.getSourceUri());
}
});
}
@Test
public void testOptimizeStorage_DeletedRecords() {
// Setup
@ -214,7 +276,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
myReindexTestHelper.createAlleleSearchParameter();
assertEquals(2, myObservationDao.search(SearchParameterMap.newSynchronous()).size());
assertEquals(2, myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd).size());
// The search param value is on the observation, but it hasn't been indexed yet
assertThat(myReindexTestHelper.getAlleleObservationIds(), hasSize(0));
@ -230,7 +292,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
myBatch2JobHelper.awaitJobCompletion(res);
// validate
assertEquals(2, myObservationDao.search(SearchParameterMap.newSynchronous()).size());
assertEquals(2, myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd).size());
// Now one of them should be indexed
List<String> alleleObservationIds = myReindexTestHelper.getAlleleObservationIds();
@ -249,7 +311,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
assertThat(entriesInSpIndexTokenTable, equalTo(1));
// simulate resource deletion
ResourceTable resource = myResourceTableDao.findById(obsId.getIdPartAsLong()).get();
ResourceTable resource = myResourceTableDao.findById(obsId.getIdPartAsLong()).orElseThrow();
Date currentDate = new Date();
resource.setDeleted(currentDate);
resource.setUpdated(currentDate);
@ -288,7 +350,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
myReindexTestHelper.createAlleleSearchParameter();
mySearchParamRegistry.forceRefresh();
assertEquals(50, myObservationDao.search(SearchParameterMap.newSynchronous()).size());
assertEquals(50, myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd).size());
// The search param value is on the observation, but it hasn't been indexed yet
assertThat(myReindexTestHelper.getAlleleObservationIds(), hasSize(0));
@ -300,7 +362,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
myBatch2JobHelper.awaitJobCompletion(startResponse);
// validate
assertEquals(50, myObservationDao.search(SearchParameterMap.newSynchronous()).size());
assertEquals(50, myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd).size());
// Now all of them should be indexed
assertThat(myReindexTestHelper.getAlleleObservationIds(), hasSize(50));
}
@ -331,7 +393,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
// Verify
assertEquals(StatusEnum.COMPLETED, outcome.getStatus());
assertEquals(null, outcome.getErrorMessage());
assertNull(outcome.getErrorMessage());
}
@Test

View File

@ -283,7 +283,7 @@ public class PartitioningInterceptorR4Test extends BaseJpaR4SystemTest {
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
}
private Consumer<IBaseResource> withPartition(Integer thePartitionId) {
private ICreationArgument withPartition(Integer thePartitionId) {
return t -> {
if (thePartitionId != null) {
addCreatePartition(thePartitionId, null);

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Observation;
@ -142,7 +143,7 @@ public class AuthorizationInterceptorMultitenantJpaR4Test extends BaseMultitenan
Bundle output = myClient
.search()
.forResource("Observation")
.include(Observation.INCLUDE_ALL)
.include(IBaseResource.INCLUDE_ALL)
.returnBundle(Bundle.class)
.execute();
assertEquals(2, output.getEntry().size());
@ -165,7 +166,7 @@ public class AuthorizationInterceptorMultitenantJpaR4Test extends BaseMultitenan
myClient
.search()
.forResource("Observation")
.include(Observation.INCLUDE_ALL)
.include(IBaseResource.INCLUDE_ALL)
.returnBundle(Bundle.class)
.execute();
fail();
@ -198,7 +199,7 @@ public class AuthorizationInterceptorMultitenantJpaR4Test extends BaseMultitenan
Bundle bundle = myClient
.search()
.forResource("Observation")
.include(Observation.INCLUDE_ALL)
.include(IBaseResource.INCLUDE_ALL)
.sort().ascending(Observation.IDENTIFIER)
.returnBundle(Bundle.class)
.count(3)

View File

@ -25,7 +25,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.DEFAULT_PARTITION_NAME;
@ -124,7 +123,7 @@ public abstract class BaseMultitenantResourceProviderR4Test extends BaseResource
protected Consumer<IBaseResource> withTenant(String theTenantId) {
protected ICreationArgument withTenant(String theTenantId) {
return t -> myTenantClientInterceptor.setTenantId(theTenantId);
}

View File

@ -364,7 +364,7 @@ public class GiantTransactionPerfTest {
}
@Override
public Slice<ResourceHistoryTable> findForResourceIdAndReturnEntities(Pageable thePage, Long theId, Long theDontWantVersion) {
public Slice<ResourceHistoryTable> findForResourceIdAndReturnEntitiesAndFetchProvenance(Pageable thePage, Long theId, Long theDontWantVersion) {
throw new UnsupportedOperationException();
}
@ -383,11 +383,6 @@ public class GiantTransactionPerfTest {
throw new UnsupportedOperationException();
}
@Override
public void setResourceTextVcForVersion(Long id, String resourceText) {
throw new UnsupportedOperationException();
}
@Override
public void updateVersion(long theId, long theOldVersion, long theNewVersion) {
throw new UnsupportedOperationException();

View File

@ -28,7 +28,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SuppressWarnings({"unchecked", "SqlDialectInspection"})
@SuppressWarnings({"SqlDialectInspection"})
public class InstanceReindexServiceImplR5Test extends BaseJpaR5Test {
@Autowired
@ -125,7 +125,7 @@ public class InstanceReindexServiceImplR5Test extends BaseJpaR5Test {
@Test
public void testDryRunTypes_Number() {
IIdType id = createResource("ResearchStudy", withPrimitiveAttribute("recruitment.targetNumber", "3"));
IIdType id = createResource("ResearchStudy", withResourcePrimitiveAttribute("recruitment.targetNumber", "3"));
logAllNumberIndexes();
@ -269,7 +269,7 @@ public class InstanceReindexServiceImplR5Test extends BaseJpaR5Test {
@Test
public void testDryRunTypes_Uri() {
IIdType id = createResource("CodeSystem", withPrimitiveAttribute("url", "http://foo"));
IIdType id = createResource("CodeSystem", withResourcePrimitiveAttribute("url", "http://foo"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));

View File

@ -42,6 +42,7 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IMdmLinkJpaRepository;
import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryProvenanceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
@ -418,6 +419,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
@Autowired
protected IResourceHistoryTableDao myResourceHistoryTableDao;
@Autowired
protected IResourceHistoryProvenanceDao myResourceHistoryProvenanceDao;
@Autowired
protected IForcedIdDao myForcedIdDao;
@Autowired
@Qualifier("myCoverageDaoR4")

View File

@ -113,8 +113,8 @@ public class CommonConfig {
}
@Bean
public IBalpAuditEventSink balpAuditEventSink(FhirContext theFhirContext) {
return new AsyncMemoryQueueBackedFhirClientBalpSink(theFhirContext, "http://localhost:8000/baseAudit");
public IBalpAuditEventSink balpAuditEventSink() {
return new AsyncMemoryQueueBackedFhirClientBalpSink(FhirContext.forR4Cached(), "http://localhost:8000/baseAudit");
}
@Bean

View File

@ -95,7 +95,7 @@ public class AsyncMemoryQueueBackedFhirClientBalpSink extends FhirClientBalpSink
*/
public AsyncMemoryQueueBackedFhirClientBalpSink(IGenericClient theClient) {
super(theClient);
myThreadPool = ThreadPoolUtil.newThreadPool(1, 1, "BalpClientSink-" + ourNextThreadId.getAndIncrement() + "-", 100);
myThreadPool = ThreadPoolUtil.newThreadPool(1, 1, "BalpClientSink-" + ourNextThreadId.getAndIncrement() + "-", Integer.MAX_VALUE);
}
@Override

View File

@ -26,12 +26,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.MetaUtil;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.InstantType;
import org.slf4j.Logger;
@ -54,21 +49,33 @@ import static org.hamcrest.Matchers.matchesPattern;
public interface ITestDataBuilder {
Logger ourLog = LoggerFactory.getLogger(ITestDataBuilder.class);
/**
* Name chosen to avoid potential for conflict. This is an internal API to this interface.
*/
static void __setPrimitiveChild(FhirContext theFhirContext, IBase theTarget, String theElementName, String theElementType, String theValue) {
BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) theFhirContext.getElementDefinition(theTarget.getClass());
BaseRuntimeChildDefinition activeChild = def.getChildByName(theElementName);
IPrimitiveType<?> booleanType = (IPrimitiveType<?>) activeChild.getChildByName(theElementName).newInstance();
booleanType.setValueAsString(theValue);
activeChild.getMutator().addValue(theTarget, booleanType);
}
/**
* Set Patient.active = true
*/
default Consumer<IBaseResource> withActiveTrue() {
default ICreationArgument withActiveTrue() {
return t -> __setPrimitiveChild(getFhirContext(), t, "active", "boolean", "true");
}
/**
* Set Patient.active = false
*/
default Consumer<IBaseResource> withActiveFalse() {
default ICreationArgument withActiveFalse() {
return t -> __setPrimitiveChild(getFhirContext(), t, "active", "boolean", "false");
}
default Consumer<IBaseResource> withFamily(String theFamily) {
default ICreationArgument withFamily(String theFamily) {
return t -> {
IPrimitiveType<?> family = (IPrimitiveType<?>) getFhirContext().getElementDefinition("string").newInstance();
family.setValueAsString(theFamily);
@ -77,50 +84,50 @@ public interface ITestDataBuilder {
ICompositeType humanName = (ICompositeType) humanNameDef.newInstance();
humanNameDef.getChildByName("family").getMutator().addValue(humanName, family);
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(t.getClass());
BaseRuntimeElementCompositeDefinition resourceDef = (BaseRuntimeElementCompositeDefinition) getFhirContext().getElementDefinition(t.getClass());
resourceDef.getChildByName("name").getMutator().addValue(t, humanName);
};
}
/** Patient.name.given */
default <T extends IBaseResource> Consumer<T> withGiven(String theName) {
return withPrimitiveAttribute("name.given", theName);
/**
* Patient.name.given
*/
default ICreationArgument withGiven(String theName) {
return withResourcePrimitiveAttribute("name.given", theName);
}
/**
* Set Patient.birthdate
*/
default Consumer<IBaseResource> withBirthdate(String theBirthdate) {
default ICreationArgument withBirthdate(String theBirthdate) {
return t -> __setPrimitiveChild(getFhirContext(), t, "birthDate", "dateTime", theBirthdate);
}
/**
* Set Observation.status
*/
default Consumer<IBaseResource> withStatus(String theStatus) {
default ICreationArgument withStatus(String theStatus) {
return t -> __setPrimitiveChild(getFhirContext(), t, "status", "code", theStatus);
}
/**
* Set Observation.effectiveDate
*/
default Consumer<IBaseResource> withEffectiveDate(String theDate) {
default ICreationArgument withEffectiveDate(String theDate) {
return t -> __setPrimitiveChild(getFhirContext(), t, "effectiveDateTime", "dateTime", theDate);
}
/**
* Set Observation.effectiveDate
*/
default Consumer<IBaseResource> withDateTimeAt(String thePath, String theDate) {
default ICreationArgument withDateTimeAt(String thePath, String theDate) {
return t -> __setPrimitiveChild(getFhirContext(), t, thePath, "dateTime", theDate);
}
/**
* Set [Resource].identifier.system and [Resource].identifier.value
*/
default Consumer<IBaseResource> withIdentifier(String theSystem, String theValue) {
default ICreationArgument withIdentifier(String theSystem, String theValue) {
return t -> {
IPrimitiveType<?> system = (IPrimitiveType<?>) getFhirContext().getElementDefinition("uri").newInstance();
system.setValueAsString(theSystem);
@ -133,7 +140,7 @@ public interface ITestDataBuilder {
identifierDef.getChildByName("system").getMutator().addValue(identifier, system);
identifierDef.getChildByName("value").getMutator().addValue(identifier, value);
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(t.getClass());
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition((Class<? extends IBaseResource>) t.getClass());
resourceDef.getChildByName("identifier").getMutator().addValue(t, identifier);
};
}
@ -141,73 +148,74 @@ public interface ITestDataBuilder {
/**
* Set Organization.name
*/
default Consumer<IBaseResource> withName(String theStatus) {
default ICreationArgument withName(String theStatus) {
return t -> __setPrimitiveChild(getFhirContext(), t, "name", "string", theStatus);
}
default Consumer<IBaseResource> withId(String theId) {
default ICreationArgument withId(String theId) {
return t -> {
assertThat(theId, matchesPattern("[a-zA-Z0-9-]+"));
t.setId(theId);
((IBaseResource)t).setId(theId);
};
}
default Consumer<IBaseResource> withId(IIdType theId) {
return t -> t.setId(theId.toUnqualifiedVersionless());
default ICreationArgument withId(IIdType theId) {
return t -> ((IBaseResource)t).setId(theId.toUnqualifiedVersionless());
}
default Consumer<IBaseResource> withTag(String theSystem, String theCode) {
return t -> t.getMeta().addTag().setSystem(theSystem).setCode(theCode);
default ICreationArgument withTag(String theSystem, String theCode) {
return t -> ((IBaseResource)t).getMeta().addTag().setSystem(theSystem).setCode(theCode);
}
default Consumer<IBaseResource> withSecurity(String theSystem, String theCode) {
return t -> t.getMeta().addSecurity().setSystem(theSystem).setCode(theCode);
default ICreationArgument withSecurity(String theSystem, String theCode) {
return t -> ((IBaseResource)t).getMeta().addSecurity().setSystem(theSystem).setCode(theCode);
}
default Consumer<IBaseResource> withProfile(String theProfile) {
return t -> t.getMeta().addProfile(theProfile);
default ICreationArgument withProfile(String theProfile) {
return t -> ((IBaseResource)t).getMeta().addProfile(theProfile);
}
default Consumer<IBaseResource> withSource(FhirContext theContext, String theSource) {
return t -> MetaUtil.setSource(theContext, t.getMeta(), theSource);
default ICreationArgument withSource(FhirContext theContext, String theSource) {
return t -> MetaUtil.setSource(theContext, ((IBaseResource)t).getMeta(), theSource);
}
default Consumer<IBaseResource> withLastUpdated(Date theLastUpdated) {
return t -> t.getMeta().setLastUpdated(theLastUpdated);
default ICreationArgument withLastUpdated(Date theLastUpdated) {
return t -> ((IBaseResource)t).getMeta().setLastUpdated(theLastUpdated);
}
default Consumer<IBaseResource> withLastUpdated(String theIsoDate) {
return t -> t.getMeta().setLastUpdated(new InstantType(theIsoDate).getValue());
default ICreationArgument withLastUpdated(String theIsoDate) {
return t -> ((IBaseResource)t).getMeta().setLastUpdated(new InstantType(theIsoDate).getValue());
}
default IIdType createEncounter(Consumer<IBaseResource>... theModifiers) {
default IIdType createEncounter(ICreationArgument... theModifiers) {
return createResource("Encounter", theModifiers);
}
default IIdType createGroup(Consumer<IBaseResource>... theModifiers) {
default IIdType createGroup(ICreationArgument... theModifiers) {
return createResource("Group", theModifiers);
}
default IIdType createObservation(Consumer<IBaseResource>... theModifiers) {
default IIdType createObservation(ICreationArgument... theModifiers) {
return createResource("Observation", theModifiers);
}
default IIdType createObservation(Collection<Consumer<IBaseResource>> theModifiers) {
return createResource("Observation", theModifiers.toArray(new Consumer[0]));
default IIdType createObservation(Collection<ICreationArgument> theModifiers) {
return createResource("Observation", theModifiers.toArray(new ICreationArgument[0]));
}
default IBaseResource buildPatient(Consumer<IBaseResource>... theModifiers) {
default IBaseResource buildPatient(ICreationArgument... theModifiers) {
return buildResource("Patient", theModifiers);
}
default IIdType createPatient(Consumer<IBaseResource>... theModifiers) {
default IIdType createPatient(ICreationArgument... theModifiers) {
return createResource("Patient", theModifiers);
}
default IIdType createOrganization(Consumer<IBaseResource>... theModifiers) {
default IIdType createOrganization(ICreationArgument... theModifiers) {
return createResource("Organization", theModifiers);
}
default IIdType createResource(String theResourceType, Consumer<IBaseResource>... theModifiers) {
default IIdType createResource(String theResourceType, ICreationArgument... theModifiers) {
IBaseResource resource = buildResource(theResourceType, theModifiers);
if (ourLog.isDebugEnabled()) {
@ -221,7 +229,7 @@ public interface ITestDataBuilder {
}
}
default IIdType createResourceFromJson(String theJson, Consumer<IBaseResource>... theModifiers) {
default IIdType createResourceFromJson(String theJson, ICreationArgument... theModifiers) {
IBaseResource resource = getFhirContext().newJsonParser().parseResource(theJson);
applyElementModifiers(resource, theModifiers);
@ -236,70 +244,76 @@ public interface ITestDataBuilder {
}
}
default <T extends IBaseResource> T buildResource(String theResourceType, Consumer<IBaseResource>... theModifiers) {
default <T extends IBaseResource> T buildResource(String theResourceType, ICreationArgument... theModifiers) {
IBaseResource resource = getFhirContext().getResourceDefinition(theResourceType).newInstance();
applyElementModifiers(resource, theModifiers);
return (T) resource;
}
default Consumer<IBaseResource> withSubject(@Nullable IIdType theSubject) {
default ICreationArgument withSubject(@Nullable IIdType theSubject) {
return withReference("subject", theSubject);
}
default Consumer<IBaseResource> withSubject(@Nullable String theSubject) {
default ICreationArgument withSubject(@Nullable String theSubject) {
return withSubject(new IdType(theSubject));
}
default Consumer<IBaseResource> withPatient(@Nullable IIdType theSubject) {
default ICreationArgument withPatient(@Nullable IIdType theSubject) {
return withReference("patient", theSubject);
}
default Consumer<IBaseResource> withPatient(@Nullable String theSubject) {
default ICreationArgument withPatient(@Nullable String theSubject) {
return withSubject(new IdType(theSubject));
}
default Consumer<IBaseResource> withGroupMember(@Nullable IIdType theMember) {
return withPrimitiveAttribute("member.entity.reference", theMember);
default ICreationArgument withGroupMember(@Nullable IIdType theMember) {
return withResourcePrimitiveAttribute("member.entity.reference", theMember);
}
default Consumer<IBaseResource> withGroupMember(@Nullable String theMember) {
default ICreationArgument withGroupMember(@Nullable String theMember) {
return withGroupMember(new IdType(theMember));
}
default Consumer<IBaseResource> withEncounter(@Nullable String theEncounter) {
default ICreationArgument withEncounter(@Nullable String theEncounter) {
return withReference("encounter", new IdType(theEncounter));
}
@Nonnull
private Consumer<IBaseResource> withReference(String theReferenceName, @Nullable IIdType theReferenceValue) {
private ICreationArgument withReference(String theReferenceName, @Nullable IIdType theReferenceValue) {
return t -> {
if (theReferenceValue != null && theReferenceValue.getValue() != null) {
IBaseReference reference = (IBaseReference) getFhirContext().getElementDefinition("Reference").newInstance();
reference.setReference(theReferenceValue.getValue());
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(t);
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition((IBaseResource) t);
resourceDef.getChildByName(theReferenceName).getMutator().addValue(t, reference);
}
};
}
default <T extends IBase> Consumer<T> withPrimitiveAttribute(String thePath, Object theValue) {
return t->{
default Consumer<IBase> withPrimitiveAttribute(String thePath, Object theValue) {
return t -> {
FhirTerser terser = getFhirContext().newTerser();
terser.addElement(t, thePath, ""+theValue);
terser.addElement(t, thePath, "" + theValue);
};
}
default <T extends IBase, E extends IBase> Consumer<T> withElementAt(String thePath, Consumer<E>... theModifiers) {
return t->{
default ICreationArgument withResourcePrimitiveAttribute(String thePath, Object theValue) {
return t -> {
FhirTerser terser = getFhirContext().newTerser();
terser.addElement(t, thePath, "" + theValue);
};
}
default <E extends IBase> ICreationArgument withElementAt(String thePath, Consumer<E>... theModifiers) {
return t -> {
FhirTerser terser = getFhirContext().newTerser();
E element = terser.addElement(t, thePath);
applyElementModifiers(element, theModifiers);
};
}
default <T extends IBase> Consumer<T> withQuantityAtPath(String thePath, Number theValue, String theSystem, String theCode) {
default ICreationArgument withQuantityAtPath(String thePath, Number theValue, String theSystem, String theCode) {
return withElementAt(thePath,
withPrimitiveAttribute("value", theValue),
withPrimitiveAttribute("system", theSystem),
@ -307,11 +321,11 @@ public interface ITestDataBuilder {
);
}
/**
* Create an Element and apply modifiers
*
* @param theElementType the FHIR Element type to create
* @param theModifiers modifiers to apply after construction
* @param theModifiers modifiers to apply after construction
* @return the Element
*/
default IBase withElementOfType(String theElementType, Consumer<IBase>... theModifiers) {
@ -326,19 +340,19 @@ public interface ITestDataBuilder {
}
}
default Consumer<IBaseResource> withObservationCode(@Nullable String theSystem, @Nullable String theCode) {
default ICreationArgument withObservationCode(@Nullable String theSystem, @Nullable String theCode) {
return withObservationCode(theSystem, theCode, null);
}
default Consumer<IBaseResource> withObservationCode(@Nullable String theSystem, @Nullable String theCode, @Nullable String theDisplay) {
default ICreationArgument withObservationCode(@Nullable String theSystem, @Nullable String theCode, @Nullable String theDisplay) {
return withCodingAt("code.coding", theSystem, theCode, theDisplay);
}
default <T extends IBase> Consumer<T> withCodingAt(String thePath, @Nullable String theSystem, @Nullable String theValue) {
default <T extends IBase> ICreationArgument withCodingAt(String thePath, @Nullable String theSystem, @Nullable String theValue) {
return withCodingAt(thePath, theSystem, theValue, null);
}
default <T extends IBase> Consumer<T> withCodingAt(String thePath, @Nullable String theSystem, @Nullable String theValue, @Nullable String theDisplay) {
default <T extends IBase> ICreationArgument withCodingAt(String thePath, @Nullable String theSystem, @Nullable String theValue, @Nullable String theDisplay) {
return withElementAt(thePath,
withPrimitiveAttribute("system", theSystem),
withPrimitiveAttribute("code", theValue),
@ -346,15 +360,15 @@ public interface ITestDataBuilder {
);
}
default <T extends IBaseResource, E extends IBase> Consumer<T> withObservationComponent(Consumer<E>... theModifiers) {
default ICreationArgument withObservationComponent(ICreationArgument... theModifiers) {
return withElementAt("component", theModifiers);
}
default Consumer<IBaseResource> withObservationHasMember(@Nullable IIdType theHasMember) {
default ICreationArgument withObservationHasMember(@Nullable IIdType theHasMember) {
return withReference("hasMember", theHasMember);
}
default Consumer<IBaseResource> withOrganization(@Nullable IIdType theHasMember) {
default ICreationArgument withOrganization(@Nullable IIdType theHasMember) {
return withReference("managingOrganization", theHasMember);
}
@ -373,21 +387,15 @@ public interface ITestDataBuilder {
*/
FhirContext getFhirContext();
/**
* Name chosen to avoid potential for conflict. This is an internal API to this interface.
*/
static void __setPrimitiveChild(FhirContext theFhirContext, IBaseResource theTarget, String theElementName, String theElementType, String theValue) {
RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theTarget.getClass());
BaseRuntimeChildDefinition activeChild = def.getChildByName(theElementName);
IPrimitiveType<?> booleanType = (IPrimitiveType<?>) activeChild.getChildByName(theElementName).newInstance();
booleanType.setValueAsString(theValue);
activeChild.getMutator().addValue(theTarget, booleanType);
default ICreationArgument[] asArray(ICreationArgument theIBaseResourceConsumer) {
return new ICreationArgument[]{theIBaseResourceConsumer};
}
interface Support {
FhirContext getFhirContext();
IIdType doCreateResource(IBaseResource theResource);
IIdType doUpdateResource(IBaseResource theResource);
}
@ -411,6 +419,10 @@ public interface ITestDataBuilder {
}
}
interface ICreationArgument extends Consumer<IBase> {
// nothing
}
/**
* Dummy support to use ITestDataBuilder as just a builder, not a DAO
*/
@ -438,4 +450,6 @@ public interface ITestDataBuilder {
return null;
}
}
}