Work on multitenancy

This commit is contained in:
jamesagnew 2020-03-27 09:22:00 -04:00
parent ec6fe70acb
commit 9df4c58122
5 changed files with 66 additions and 43 deletions

View File

@ -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."

View File

@ -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;
} }

View File

@ -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

View File

@ -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();

View File

@ -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;