Work on multitenancy
This commit is contained in:
parent
ec6fe70acb
commit
9df4c58122
|
@ -25,8 +25,3 @@
|
||||||
has been replaced with an equivalent `FhirContext.newFhirPath()`. The FhirPath expression language was initially
|
has been replaced with an equivalent `FhirContext.newFhirPath()`. The FhirPath expression language was initially
|
||||||
called FluentPath before being renamed, so this change brings HAPI FHIR inline with the correct naming.
|
called FluentPath before being renamed, so this change brings HAPI FHIR inline with the correct naming.
|
||||||
"
|
"
|
||||||
- item:
|
|
||||||
type: change
|
|
||||||
title: "The JPA server now shared a single set of tags for all versions of a resource, bringing it in line with the
|
|
||||||
functional description in the FHIR specification. This means that it is no longer possible to modify the tags for
|
|
||||||
a specific version of a resource, and also causes significant performance improvements in some cases."
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.Test;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -36,16 +37,19 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class MultitenantR4Test extends BaseJpaR4SystemTest {
|
public class MultitenantR4Test extends BaseJpaR4SystemTest {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MultitenantR4Test.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MultitenantR4Test.class);
|
||||||
|
private MyInterceptor myTenantInterceptor;
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void after() {
|
public void after() {
|
||||||
myDaoConfig.setMultiTenancyEnabled(new DaoConfig().isMultiTenancyEnabled());
|
myDaoConfig.setMultiTenancyEnabled(new DaoConfig().isMultiTenancyEnabled());
|
||||||
|
|
||||||
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
|
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
|
||||||
|
myInterceptor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,56 +78,84 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
|
||||||
public void testCreateResourceWithTenant() {
|
public void testCreateResourceWithTenant() {
|
||||||
createUniqueCompositeSp();
|
createUniqueCompositeSp();
|
||||||
|
|
||||||
int expectId = 3;
|
addTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
|
||||||
LocalDate expectDate = LocalDate.of(2020, Month.JANUARY, 14);
|
addTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
|
||||||
myInterceptorRegistry.registerInterceptor(new MyInterceptor(new TenantId(expectId, expectDate)));
|
|
||||||
|
|
||||||
Organization org = new Organization();
|
Organization org = new Organization();
|
||||||
org.setName("org");
|
org.setName("org");
|
||||||
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
|
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
|
p.getMeta().addTag("http://system", "code", "diisplay");
|
||||||
p.addName().setFamily("FAM");
|
p.addName().setFamily("FAM");
|
||||||
p.addIdentifier().setSystem("system").setValue("value");
|
p.addIdentifier().setSystem("system").setValue("value");
|
||||||
p.setBirthDate(new Date());
|
p.setBirthDate(new Date());
|
||||||
p.getManagingOrganization().setReferenceElement(orgId);
|
p.getManagingOrganization().setReferenceElement(orgId);
|
||||||
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
|
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
|
||||||
|
Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
|
||||||
|
|
||||||
runInTransaction(() -> {
|
runInTransaction(() -> {
|
||||||
// HFJ_RESOURCE
|
// HFJ_RESOURCE
|
||||||
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
|
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
|
||||||
assertEquals(expectId, resourceTable.getTenantId().getTenantId().intValue());
|
assertEquals(3, resourceTable.getTenantId().getTenantId().intValue());
|
||||||
assertEquals(expectDate, resourceTable.getTenantId().getTenantDate());
|
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), resourceTable.getTenantId().getTenantDate());
|
||||||
|
|
||||||
|
resourceTable.getProfile()
|
||||||
|
|
||||||
// HFJ_RES_VER
|
// HFJ_RES_VER
|
||||||
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
|
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
|
||||||
assertEquals(expectId, version.getTenantId().getTenantId().intValue());
|
assertEquals(3, version.getTenantId().getTenantId().intValue());
|
||||||
assertEquals(expectDate, version.getTenantId().getTenantDate());
|
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), version.getTenantId().getTenantDate());
|
||||||
|
|
||||||
// HFJ_SPIDX_STRING
|
// HFJ_SPIDX_STRING
|
||||||
List<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
|
List<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
|
||||||
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
|
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
|
||||||
assertEquals(10, strings.size());
|
assertEquals(10, strings.size());
|
||||||
assertEquals(expectId, strings.get(0).getTenantId().getTenantId().intValue());
|
assertEquals(3, strings.get(0).getTenantId().getTenantId().intValue());
|
||||||
assertEquals(expectDate, strings.get(0).getTenantId().getTenantDate());
|
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), strings.get(0).getTenantId().getTenantDate());
|
||||||
|
|
||||||
// HFJ_RES_LINK
|
// HFJ_RES_LINK
|
||||||
List<ResourceLink> resourceLinks = myResourceLinkDao.findAllForResourceId(patientId);
|
List<ResourceLink> resourceLinks = myResourceLinkDao.findAllForResourceId(patientId);
|
||||||
assertEquals(1, resourceLinks.size());
|
assertEquals(1, resourceLinks.size());
|
||||||
assertEquals(expectId, resourceLinks.get(0).getTenantId().getTenantId().intValue());
|
assertEquals(3, resourceLinks.get(0).getTenantId().getTenantId().intValue());
|
||||||
assertEquals(expectDate, resourceLinks.get(0).getTenantId().getTenantDate());
|
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), resourceLinks.get(0).getTenantId().getTenantDate());
|
||||||
|
|
||||||
// HFJ_RES_PARAM_PRESENT
|
// HFJ_RES_PARAM_PRESENT
|
||||||
List<SearchParamPresent> presents = mySearchParamPresentDao.findAllForResource(resourceTable);
|
List<SearchParamPresent> presents = mySearchParamPresentDao.findAllForResource(resourceTable);
|
||||||
assertEquals(3, presents.size());
|
assertEquals(3, presents.size());
|
||||||
assertEquals(expectId, presents.get(0).getTenantId().getTenantId().intValue());
|
assertEquals(3, presents.get(0).getTenantId().getTenantId().intValue());
|
||||||
assertEquals(expectDate, presents.get(0).getTenantId().getTenantDate());
|
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), presents.get(0).getTenantId().getTenantDate());
|
||||||
|
|
||||||
// HFJ_IDX_CMP_STRING_UNIQ
|
// HFJ_IDX_CMP_STRING_UNIQ
|
||||||
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId);
|
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId);
|
||||||
assertEquals(3, uniques.size());
|
assertEquals(3, uniques.size());
|
||||||
assertEquals(expectId, uniques.get(0).getTenantId().getTenantId().intValue());
|
assertEquals(3, uniques.get(0).getTenantId().getTenantId().intValue());
|
||||||
assertEquals(expectDate, uniques.get(0).getTenantId().getTenantDate());
|
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), uniques.get(0).getTenantId().getTenantDate());
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateResourceWithTenant() {
|
||||||
|
createUniqueCompositeSp();
|
||||||
|
|
||||||
|
addTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setActive(true);
|
||||||
|
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.setId("Patient/" + patientId);
|
||||||
|
p.setActive(false);
|
||||||
|
myPatientDao.update(p);
|
||||||
|
|
||||||
|
runInTransaction(() -> {
|
||||||
|
// HFJ_RES_VER
|
||||||
|
ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 2);
|
||||||
|
assertEquals(tenantId, resVer.getTenantId().getTenantId().intValue());
|
||||||
|
assertEquals(tenantDate, resVer.getTenantId().getTenantDate());
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -154,19 +186,27 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
|
||||||
mySearchParamRegistry.forceRefresh();
|
mySearchParamRegistry.forceRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addTenant(int theTenantId, LocalDate theTenantDate) {
|
||||||
|
if (myTenantInterceptor == null) {
|
||||||
|
myTenantInterceptor = new MyInterceptor();
|
||||||
|
myInterceptorRegistry.registerInterceptor(myInterceptor);
|
||||||
|
}
|
||||||
|
myTenantInterceptor.addTenant(new TenantId(theTenantId, theTenantDate));
|
||||||
|
}
|
||||||
|
|
||||||
@Interceptor
|
@Interceptor
|
||||||
public static class MyInterceptor {
|
public static class MyInterceptor {
|
||||||
|
|
||||||
private final List<TenantId> myTenantIds;
|
private final List<TenantId> myTenantIds = new ArrayList<>();
|
||||||
|
|
||||||
public MyInterceptor(TenantId theTenantId) {
|
public void addTenant(TenantId theTenantId) {
|
||||||
Validate.notNull(theTenantId);
|
Validate.notNull(theTenantId);
|
||||||
myTenantIds = Collections.singletonList(theTenantId);
|
myTenantIds.add(theTenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE)
|
@Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE)
|
||||||
public TenantId tenantIdentifyCreate() {
|
public TenantId tenantIdentifyCreate() {
|
||||||
TenantId retVal = myTenantIds.get(0);
|
TenantId retVal = myTenantIds.remove(0);
|
||||||
ourLog.info("Returning tenant ID: {}", retVal);
|
ourLog.info("Returning tenant ID: {}", retVal);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,10 +57,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
init400(); // 20190401 - 20190814
|
init400(); // 20190401 - 20190814
|
||||||
init410(); // 20190815 - 20191014
|
init410(); // 20190815 - 20191014
|
||||||
init420(); // 20191015 - 20200217
|
init420(); // 20191015 - 20200217
|
||||||
init430(); // 20200218 - present
|
init500(); // 20200218 - present
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init430() { // 20200218 - present
|
protected void init500() { // 20200218 - present
|
||||||
Builder version = forVersion(VersionEnum.V4_3_0);
|
Builder version = forVersion(VersionEnum.V4_3_0);
|
||||||
|
|
||||||
// Eliminate circular dependency.
|
// Eliminate circular dependency.
|
||||||
|
@ -68,6 +68,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
version.onTable("HFJ_RES_VER").dropColumn("20200218.2", "FORCED_ID_PID");
|
version.onTable("HFJ_RES_VER").dropColumn("20200218.2", "FORCED_ID_PID");
|
||||||
version.onTable("HFJ_RES_VER").addForeignKey("20200218.3", "FK_RESOURCE_HISTORY_RESOURCE").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
|
version.onTable("HFJ_RES_VER").addForeignKey("20200218.3", "FK_RESOURCE_HISTORY_RESOURCE").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
|
||||||
version.onTable("HFJ_RES_VER").modifyColumn("20200220.1", "RES_ID").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
|
version.onTable("HFJ_RES_VER").modifyColumn("20200220.1", "RES_ID").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
|
||||||
|
|
||||||
|
// Add mlutiitenancy
|
||||||
|
version.onTable("HFJ_RESOURCE").dropColumn("20200327.1", "RES_PROFILE");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init420() { // 20191015 - 20200217
|
protected void init420() { // 20191015 - 20200217
|
||||||
|
|
|
@ -60,7 +60,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource, IResourceLookup {
|
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource, IResourceLookup {
|
||||||
public static final int RESTYPE_LEN = 40;
|
public static final int RESTYPE_LEN = 40;
|
||||||
private static final int MAX_LANGUAGE_LENGTH = 20;
|
private static final int MAX_LANGUAGE_LENGTH = 20;
|
||||||
private static final int MAX_PROFILE_LENGTH = 200;
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -167,10 +166,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
@OptimisticLock(excluded = true)
|
@OptimisticLock(excluded = true)
|
||||||
private boolean myParamsUriPopulated;
|
private boolean myParamsUriPopulated;
|
||||||
|
|
||||||
@Column(name = "RES_PROFILE", length = MAX_PROFILE_LENGTH, nullable = true)
|
|
||||||
@OptimisticLock(excluded = true)
|
|
||||||
private String myProfile;
|
|
||||||
|
|
||||||
// Added in 3.0.0 - Should make this a primitive Boolean at some point
|
// Added in 3.0.0 - Should make this a primitive Boolean at some point
|
||||||
@OptimisticLock(excluded = true)
|
@OptimisticLock(excluded = true)
|
||||||
@Column(name = "SP_CMPSTR_UNIQ_PRESENT")
|
@Column(name = "SP_CMPSTR_UNIQ_PRESENT")
|
||||||
|
@ -402,17 +397,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
getParamsUri().addAll(theParamsUri);
|
getParamsUri().addAll(theParamsUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProfile() {
|
|
||||||
return myProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProfile(String theProfile) {
|
|
||||||
if (defaultString(theProfile).length() > MAX_PROFILE_LENGTH) {
|
|
||||||
throw new UnprocessableEntityException("Profile name exceeds maximum length of " + MAX_PROFILE_LENGTH + " chars: " + theProfile);
|
|
||||||
}
|
|
||||||
myProfile = theProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getResourceId() {
|
public Long getResourceId() {
|
||||||
return getId();
|
return getId();
|
||||||
|
|
|
@ -50,6 +50,7 @@ public class ResourceTag extends BaseTag {
|
||||||
|
|
||||||
@Column(name = "RES_ID", insertable = false, updatable = false)
|
@Column(name = "RES_ID", insertable = false, updatable = false)
|
||||||
private Long myResourceId;
|
private Long myResourceId;
|
||||||
|
|
||||||
@Embedded
|
@Embedded
|
||||||
private TenantId myTenantId;
|
private TenantId myTenantId;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue