Work on muiltitenancy

This commit is contained in:
jamesagnew 2020-03-29 13:35:20 -04:00
parent 9df4c58122
commit 691f2c4e9a
17 changed files with 275 additions and 70 deletions

View File

@ -67,6 +67,12 @@
<version>${project.version}</version> <version>${project.version}</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r5</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId> <artifactId>hapi-fhir-validation-resources-dstu2</artifactId>

View File

@ -0,0 +1,7 @@
---
type: add
issue: 1783
title: "In the JPA sevrer, if overriding built-in search parameters is not enabled, the server
will now return an error if a client tries to create a SearchParameter that is
trying to override one. Previously, the SearchParameter would be stored but silently ignored,
which was confusing."

View File

@ -219,6 +219,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
retVal.setResourceType(theEntity.getResourceType()); retVal.setResourceType(theEntity.getResourceType());
retVal.setForcedId(theId.getIdPart()); retVal.setForcedId(theId.getIdPart());
retVal.setResource(theEntity); retVal.setResource(theEntity);
retVal.setTenantId(theEntity.getTenantId());
theEntity.setForcedId(retVal); theEntity.setForcedId(retVal);
} }
} }
@ -1094,6 +1095,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity(); ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity();
provenance.setResourceHistoryTable(historyEntry); provenance.setResourceHistoryTable(historyEntry);
provenance.setResourceTable(entity); provenance.setResourceTable(entity);
provenance.setTenantId(entity.getTenantId());
if (haveRequestId) { if (haveRequestId) {
provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH)); provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH));
} }

View File

@ -24,8 +24,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.SearchParameter; import ca.uhn.fhir.model.dstu2.resource.SearchParameter;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum;
@ -38,8 +36,6 @@ import java.util.List;
public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> { public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
@Autowired
private IFhirSystemDao<Bundle, MetaDt> mySystemDao;
@Autowired @Autowired
private ISearchParamExtractor mySearchParamExtractor; private ISearchParamExtractor mySearchParamExtractor;
@ -79,8 +75,9 @@ public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao
String expression = theResource.getXpath(); String expression = theResource.getXpath();
FhirContext context = getContext(); FhirContext context = getContext();
SearchParamTypeEnum type = theResource.getTypeElement().getValueAsEnum(); SearchParamTypeEnum type = theResource.getTypeElement().getValueAsEnum();
String code = theResource.getCode();
FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig());
} }

View File

@ -20,15 +20,19 @@ package ca.uhn.fhir.jpa.dao.data;
* #L% * #L%
*/ */
import org.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IResourceIndexedSearchParamDateDao extends JpaRepository<ResourceIndexedSearchParamDate, Long> { public interface IResourceIndexedSearchParamDateDao extends JpaRepository<ResourceIndexedSearchParamDate, Long> {
@Modifying @Modifying
@Query("delete from ResourceIndexedSearchParamDate t WHERE t.myResourcePid = :resid") @Query("delete from ResourceIndexedSearchParamDate t WHERE t.myResourcePid = :resid")
void deleteByResourceId(@Param("resid") Long theResourcePid); void deleteByResourceId(@Param("resid") Long theResourcePid);
@Query("SELECT t FROM ResourceIndexedSearchParamDate t WHERE t.myResourcePid = :resId")
List<ResourceIndexedSearchParamDate> findAllForResourceId(@Param("resId") Long thePatientId);
} }

View File

@ -70,8 +70,9 @@ public class FhirResourceDaoSearchParameterDstu3 extends BaseHapiFhirResourceDao
String expression = theResource.getExpression(); String expression = theResource.getExpression();
FhirContext context = getContext(); FhirContext context = getContext();
Enumerations.SearchParamType type = theResource.getType(); Enumerations.SearchParamType type = theResource.getType();
String code = theResource.getCode();
FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig());
} }
} }

View File

@ -2,11 +2,14 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;
@ -78,14 +81,15 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<Se
Enum<?> status = theResource.getStatus(); Enum<?> status = theResource.getStatus();
List<CodeType> base = theResource.getBase(); List<CodeType> base = theResource.getBase();
String code = theResource.getCode();
String expression = theResource.getExpression(); String expression = theResource.getExpression();
FhirContext context = getContext(); FhirContext context = getContext();
Enum<?> type = theResource.getType(); Enum<?> type = theResource.getType();
FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig());
} }
public static void validateSearchParam(ISearchParamExtractor theSearchParamExtractor, Enum<?> theType, Enum<?> theStatus, List<? extends IPrimitiveType> theBase, String theExpression, FhirContext theContext, DaoConfig theDaoConfig) { public static void validateSearchParam(ISearchParamRegistry theSearchParamRegistry, ISearchParamExtractor theSearchParamExtractor, String theCode, Enum<?> theType, Enum<?> theStatus, List<? extends IPrimitiveType> theBase, String theExpression, FhirContext theContext, DaoConfig theDaoConfig) {
if (theStatus == null) { if (theStatus == null) {
throw new UnprocessableEntityException("SearchParameter.status is missing or invalid"); throw new UnprocessableEntityException("SearchParameter.status is missing or invalid");
} }
@ -146,6 +150,19 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<Se
} }
} // if have expression } // if have expression
// If overriding built-in SPs is disabled on this server, make sure we aren't
// doing that
if (theDaoConfig.getModelConfig().isDefaultSearchParamsCanBeOverridden() == false) {
for (IPrimitiveType<?> nextBaseType : theBase) {
String nextBase = nextBaseType.getValueAsString();
RuntimeSearchParam existingSearchParam = theSearchParamRegistry.getActiveSearchParam(nextBase, theCode);
if (existingSearchParam.getId() == null) {
throw new UnprocessableEntityException("Can not override built-in search parameter " + nextBase + ":" + theCode + " because overriding is disabled on this server");
}
}
}
} }
} }

View File

@ -68,11 +68,12 @@ public class FhirResourceDaoSearchParameterR5 extends BaseHapiFhirResourceDao<Se
Enum<?> status = theResource.getStatus(); Enum<?> status = theResource.getStatus();
List<CodeType> base = theResource.getBase(); List<CodeType> base = theResource.getBase();
String code = theResource.getCode();
String expression = theResource.getExpression(); String expression = theResource.getExpression();
FhirContext context = getContext(); FhirContext context = getContext();
Enum<?> type = theResource.getType(); Enum<?> type = theResource.getType();
FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig());
} }

View File

@ -2,27 +2,39 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SampledData;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.*; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class); private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class);
@ -31,6 +43,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
public void afterResetDao() { public void afterResetDao() {
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy()); myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy());
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
} }
@Test @Test
@ -149,7 +162,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
p = new Patient(); p = new Patient();
p.setActive(false); p.setActive(false);
try { try {
myPatientDao.create(p).getId(); myPatientDao.create(p);
fail(); fail();
} catch (ResourceVersionConflictException e) { } catch (ResourceVersionConflictException e) {
// good // good
@ -280,6 +293,26 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
} }
@Test
public void testOverrideBuiltInSearchParamFailsIfDisabled() {
myModelConfig.setDefaultSearchParamsCanBeOverridden(false);
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate");
sp.setType(Enumerations.SearchParamType.DATE);
sp.setCode("birthdate");
sp.setExpression("Patient.birthDate");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
try {
mySearchParameterDao.update(sp);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Can not override built-in search parameter Patient:birthdate because overriding is disabled on this server", e.getMessage());
}
}
@AfterClass @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {

View File

@ -22,7 +22,6 @@ import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
@ -32,10 +31,7 @@ import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentMatchers; import org.mockito.ArgumentMatchers;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.util.ProxyUtils;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
@ -48,8 +44,17 @@ import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED; import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -695,7 +700,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertEquals(1, myResourceReindexingSvc.forceReindexingPass()); assertEquals(1, myResourceReindexingSvc.forceReindexingPass());
assertEquals(0, myResourceReindexingSvc.forceReindexingPass()); assertEquals(0, myResourceReindexingSvc.forceReindexingPass());
runInTransaction(()->{ runInTransaction(() -> {
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(uniques.toString(), 1, uniques.size()); assertEquals(uniques.toString(), 1, uniques.size());
assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart()))); assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart())));
@ -712,7 +717,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myResourceReindexingSvc.forceReindexingPass(); myResourceReindexingSvc.forceReindexingPass();
assertEquals(0, myResourceReindexingSvc.forceReindexingPass()); assertEquals(0, myResourceReindexingSvc.forceReindexingPass());
runInTransaction(()->{ runInTransaction(() -> {
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(uniques.toString(), 1, uniques.size()); assertEquals(uniques.toString(), 1, uniques.size());
assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart()))); assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart())));

View File

@ -4,8 +4,10 @@ import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@ -17,6 +19,7 @@ import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter; import org.hl7.fhir.r4.model.SearchParameter;
@ -29,13 +32,13 @@ 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -43,6 +46,8 @@ 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; private MyInterceptor myTenantInterceptor;
private LocalDate myTenantDate;
private int myTenantId;
@After @After
public void after() { public void after() {
@ -58,6 +63,11 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
super.before(); super.before();
myDaoConfig.setMultiTenancyEnabled(true); myDaoConfig.setMultiTenancyEnabled(true);
myDaoConfig.setUniqueIndexesEnabled(true);
myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
myTenantDate = LocalDate.of(2020, Month.JANUARY, 14);
myTenantId = 3;
} }
@ -74,12 +84,14 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
}); });
} }
@Test @Test
public void testCreateResourceWithTenant() { public void testCreateResourceWithTenant() {
createUniqueCompositeSp(); createUniqueCompositeSp();
createRequestId();
addTenant(3, LocalDate.of(2020, Month.JANUARY, 14)); addCreateTenant(myTenantId, myTenantDate);
addTenant(3, LocalDate.of(2020, Month.JANUARY, 14)); addCreateTenant(myTenantId, myTenantDate);
Organization org = new Organization(); Organization org = new Organization();
org.setName("org"); org.setName("org");
@ -91,75 +103,155 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
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);
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); 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(3, resourceTable.getTenantId().getTenantId().intValue()); assertEquals(myTenantId, resourceTable.getTenantId().getTenantId().intValue());
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), resourceTable.getTenantId().getTenantDate()); assertEquals(myTenantDate, resourceTable.getTenantId().getTenantDate());
resourceTable.getProfile()
// HFJ_RES_VER // HFJ_RES_VER
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L); ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
assertEquals(3, version.getTenantId().getTenantId().intValue()); assertEquals(myTenantId, version.getTenantId().getTenantId().intValue());
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), version.getTenantId().getTenantDate()); assertEquals(myTenantDate, version.getTenantId().getTenantDate());
// HFJ_RES_VER_PROV
assertNotNull(version.getProvenance());
assertEquals(myTenantId, version.getProvenance().getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, version.getProvenance().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(3, strings.get(0).getTenantId().getTenantId().intValue()); assertEquals(myTenantId, strings.get(0).getTenantId().getTenantId().intValue());
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), strings.get(0).getTenantId().getTenantDate()); assertEquals(myTenantDate, strings.get(0).getTenantId().getTenantDate());
// HFJ_SPIDX_DATE
List<ResourceIndexedSearchParamDate> dates = myResourceIndexedSearchParamDateDao.findAllForResourceId(patientId);
ourLog.info("\n * {}", dates.stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * ")));
assertEquals(2, dates.size());
assertEquals(myTenantId, dates.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, dates.get(0).getTenantId().getTenantDate());
assertEquals(myTenantId, dates.get(1).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, dates.get(1).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(3, resourceLinks.get(0).getTenantId().getTenantId().intValue()); assertEquals(myTenantId, resourceLinks.get(0).getTenantId().getTenantId().intValue());
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), resourceLinks.get(0).getTenantId().getTenantDate()); assertEquals(myTenantDate, 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(myTenantId, presents.size());
assertEquals(3, presents.get(0).getTenantId().getTenantId().intValue()); assertEquals(myTenantId, presents.get(0).getTenantId().getTenantId().intValue());
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), presents.get(0).getTenantId().getTenantDate()); assertEquals(myTenantDate, 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(1, uniques.size());
assertEquals(3, uniques.get(0).getTenantId().getTenantId().intValue()); assertEquals(myTenantId, uniques.get(0).getTenantId().getTenantId().intValue());
assertEquals(LocalDate.of(2020, Month.JANUARY, 14), uniques.get(0).getTenantId().getTenantDate()); assertEquals(myTenantDate, uniques.get(0).getTenantId().getTenantDate());
});
}
@Test
public void testCreateWithForcedId() {
addCreateTenant(myTenantId, myTenantDate);
addCreateTenant(myTenantId, myTenantDate);
Organization org = new Organization();
org.setId("org");
org.setName("org");
IIdType orgId = myOrganizationDao.update(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.setId("pat");
p.getManagingOrganization().setReferenceElement(orgId);
myPatientDao.update(p, mySrd);
runInTransaction(() -> {
// HFJ_FORCED_ID
List<ForcedId> forcedIds = myForcedIdDao.findAll();
assertEquals(2, forcedIds.size());
assertEquals(myTenantId, forcedIds.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, forcedIds.get(0).getTenantId().getTenantDate());
assertEquals(myTenantId, forcedIds.get(1).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, forcedIds.get(1).getTenantId().getTenantDate());
}); });
} }
@Test @Test
public void testUpdateResourceWithTenant() { public void testUpdateResourceWithTenant() {
createUniqueCompositeSp(); createRequestId();
addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
addTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
// Create a resource
Patient p = new Patient(); Patient p = new Patient();
p.setActive(true); p.setActive(true);
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong(); Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myTenantId, resourceTable.getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resourceTable.getTenantId().getTenantDate());
});
// Update that resource
p = new Patient(); p = new Patient();
p.setId("Patient/" + patientId); p.setId("Patient/" + patientId);
p.setActive(false); p.setActive(false);
myPatientDao.update(p); myPatientDao.update(p, mySrd);
runInTransaction(() -> { runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myTenantId, resourceTable.getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resourceTable.getTenantId().getTenantDate());
// HFJ_RES_VER // HFJ_RES_VER
ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 2); int version = 2;
assertEquals(tenantId, resVer.getTenantId().getTenantId().intValue()); ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, version);
assertEquals(tenantDate, resVer.getTenantId().getTenantDate()); assertEquals(myTenantId, resVer.getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resVer.getTenantId().getTenantDate());
// HFJ_RES_VER_PROV
assertNotNull(resVer.getProvenance());
assertNotNull(resVer.getTenantId());
assertEquals(myTenantId, resVer.getProvenance().getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resVer.getProvenance().getTenantId().getTenantDate());
// HFJ_SPIDX_STRING
List<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
assertEquals(10, strings.size());
assertEquals(myTenantId, strings.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, strings.get(0).getTenantId().getTenantDate());
}); });
} }
@Test
public void testReadAcrossTenants() {
IIdType patientId1 = createPatient(1, withActiveTrue());
IIdType patientId2 = createPatient(1, withActiveTrue());
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
IdType gotId2 = myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId2, gotId2);
}
@Test
public void testSearchAcrossAllTenants() {
}
private void createUniqueCompositeSp() { private void createUniqueCompositeSp() {
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate"); sp.setId("SearchParameter/patient-birthdate");
@ -171,7 +263,7 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
mySearchParameterDao.update(sp); mySearchParameterDao.update(sp);
sp = new SearchParameter(); sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate"); sp.setId("SearchParameter/patient-birthdate-unique");
sp.setType(Enumerations.SearchParamType.COMPOSITE); sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE); sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient"); sp.addBase("Patient");
@ -186,27 +278,48 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
} }
public void addTenant(int theTenantId, LocalDate theTenantDate) {
private void addCreateTenant(int theTenantId, LocalDate theTenantDate) {
if (myTenantInterceptor == null) { if (myTenantInterceptor == null) {
myTenantInterceptor = new MyInterceptor(); myTenantInterceptor = new MyInterceptor();
myInterceptorRegistry.registerInterceptor(myInterceptor); myInterceptorRegistry.registerInterceptor(myTenantInterceptor);
} }
myTenantInterceptor.addTenant(new TenantId(theTenantId, theTenantDate)); myTenantInterceptor.addCreateTenant(new TenantId(theTenantId, theTenantDate));
}
public IIdType createPatient(int theTenantId, Consumer<Patient>... theModifiers) {
addCreateTenant(theTenantId, null);
Patient p = new Patient();
for (Consumer<Patient> next : theModifiers) {
next.accept(p);
}
return myPatientDao.create(p).getId().toUnqualifiedVersionless();
}
public void createRequestId() {
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
}
private Consumer<Patient> withActiveTrue() {
return t->t.setActive(true);
} }
@Interceptor @Interceptor
public static class MyInterceptor { public static class MyInterceptor {
private final List<TenantId> myTenantIds = new ArrayList<>();
public void addTenant(TenantId theTenantId) { private final List<TenantId> myCreateTenantIds = new ArrayList<>();
public void addCreateTenant(TenantId theTenantId) {
Validate.notNull(theTenantId); Validate.notNull(theTenantId);
myTenantIds.add(theTenantId); myCreateTenantIds.add(theTenantId);
} }
@Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE) @Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE)
public TenantId tenantIdentifyCreate() { public TenantId tenantIdentifyCreate() {
TenantId retVal = myTenantIds.remove(0); TenantId retVal = myCreateTenantIds.remove(0);
ourLog.info("Returning tenant ID: {}", retVal); ourLog.info("Returning tenant ID: {}", retVal);
return retVal; return retVal;
} }

View File

@ -70,7 +70,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
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 // Add mlutiitenancy
version.onTable("HFJ_RESOURCE").dropColumn("20200327.1", "RES_PROFILE"); version.onTable("HFJ_RESOURCE").dropIndex("20200327.1", "IDX_RES_PROFILE");
version.onTable("HFJ_RESOURCE").dropColumn("20200327.2", "RES_PROFILE");
} }
protected void init420() { // 20191015 - 20200217 protected void init420() { // 20191015 - 20200217

View File

@ -62,6 +62,8 @@ public class ForcedId {
@ColumnDefault("''") @ColumnDefault("''")
@Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true) @Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true)
private String myResourceType; private String myResourceType;
@Embedded
private TenantId myTenantId;
/** /**
* Constructor * Constructor
@ -93,4 +95,12 @@ public class ForcedId {
public Long getId() { public Long getId() {
return myId; return myId;
} }
public TenantId getTenantId() {
return myTenantId;
}
public void setTenantId(TenantId theTenantId) {
myTenantId = theTenantId;
}
} }

View File

@ -51,18 +51,17 @@ public class ResourceHistoryProvenanceEntity {
@Embedded @Embedded
private TenantId myTenantId; private TenantId myTenantId;
public ResourceTable getResourceTable() { /**
return myResourceTable; * Constructor
*/
public ResourceHistoryProvenanceEntity() {
super();
} }
public void setResourceTable(ResourceTable theResourceTable) { public void setResourceTable(ResourceTable theResourceTable) {
myResourceTable = theResourceTable; myResourceTable = theResourceTable;
} }
public ResourceHistoryTable getResourceHistoryTable() {
return myResourceHistoryTable;
}
public void setResourceHistoryTable(ResourceHistoryTable theResourceHistoryTable) { public void setResourceHistoryTable(ResourceHistoryTable theResourceHistoryTable) {
myResourceHistoryTable = theResourceHistoryTable; myResourceHistoryTable = theResourceHistoryTable;
} }
@ -86,4 +85,12 @@ public class ResourceHistoryProvenanceEntity {
public Long getId() { public Long getId() {
return myId; return myId;
} }
public TenantId getTenantId() {
return myTenantId;
}
public void setTenantId(TenantId theTenantId) {
myTenantId = theTenantId;
}
} }

View File

@ -64,6 +64,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) { public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) {
setResource(theResource); setResource(theResource);
setIndexString(theIndexString); setIndexString(theIndexString);
setTenantId(theResource.getTenantId());
} }
public TenantId getTenantId() { public TenantId getTenantId() {

View File

@ -183,9 +183,10 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
public String toString() { public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("paramName", getParamName()); b.append("paramName", getParamName());
b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this b.append("resourceId", getResourcePid());
b.append("valueLow", new InstantDt(getValueLow())); b.append("valueLow", new InstantDt(getValueLow()));
b.append("valueHigh", new InstantDt(getValueHigh())); b.append("valueHigh", new InstantDt(getValueHigh()));
b.append("missing", isMissing());
return b.build(); return b.build();
} }

View File

@ -53,7 +53,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
@Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes = { @Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes = {
@Index(name = "IDX_RES_DATE", columnList = "RES_UPDATED"), @Index(name = "IDX_RES_DATE", columnList = "RES_UPDATED"),
@Index(name = "IDX_RES_LANG", columnList = "RES_TYPE,RES_LANGUAGE"), @Index(name = "IDX_RES_LANG", columnList = "RES_TYPE,RES_LANGUAGE"),
@Index(name = "IDX_RES_PROFILE", columnList = "RES_PROFILE"),
@Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"), @Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"),
@Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS") @Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS")
}) })