diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 09e0e7df7c6..63dd461e26e 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -67,6 +67,12 @@ ${project.version} true + + ca.uhn.hapi.fhir + hapi-fhir-structures-r5 + ${project.version} + true + ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu2 diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-fail-if-overriding-sps-is-disabled.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-fail-if-overriding-sps-is-disabled.yaml new file mode 100644 index 00000000000..3295f8fa849 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-fail-if-overriding-sps-is-disabled.yaml @@ -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." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 3a3ca82a8da..20d30f7b211 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -219,6 +219,7 @@ public abstract class BaseHapiFhirDao extends BaseStora retVal.setResourceType(theEntity.getResourceType()); retVal.setForcedId(theId.getIdPart()); retVal.setResource(theEntity); + retVal.setTenantId(theEntity.getTenantId()); theEntity.setForcedId(retVal); } } @@ -1094,6 +1095,7 @@ public abstract class BaseHapiFhirDao extends BaseStora ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity(); provenance.setResourceHistoryTable(historyEntry); provenance.setResourceTable(entity); + provenance.setTenantId(entity.getTenantId()); if (haveRequestId) { provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH)); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java index da4cacaddd1..ed542a9d3a3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java @@ -24,8 +24,6 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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.valueset.ResourceTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum; @@ -38,8 +36,6 @@ import java.util.List; public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { - @Autowired - private IFhirSystemDao mySystemDao; @Autowired private ISearchParamExtractor mySearchParamExtractor; @@ -79,8 +75,9 @@ public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao String expression = theResource.getXpath(); FhirContext context = getContext(); 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()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamDateDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamDateDao.java index c37f62e6df7..457cd6515fb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamDateDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamDateDao.java @@ -20,15 +20,19 @@ package ca.uhn.fhir.jpa.dao.data; * #L% */ -import org.springframework.data.jpa.repository.JpaRepository; - 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.Query; import org.springframework.data.repository.query.Param; +import java.util.List; + public interface IResourceIndexedSearchParamDateDao extends JpaRepository { @Modifying @Query("delete from ResourceIndexedSearchParamDate t WHERE t.myResourcePid = :resid") void deleteByResourceId(@Param("resid") Long theResourcePid); + + @Query("SELECT t FROM ResourceIndexedSearchParamDate t WHERE t.myResourcePid = :resId") + List findAllForResourceId(@Param("resId") Long thePatientId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java index 3041c843d8f..da2d908c356 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java @@ -70,8 +70,9 @@ public class FhirResourceDaoSearchParameterDstu3 extends BaseHapiFhirResourceDao String expression = theResource.getExpression(); FhirContext context = getContext(); 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()); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java index 8dab747cd37..71ac871635c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java @@ -2,11 +2,14 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; 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.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; 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.registry.ISearchParamRegistry; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.ElementUtil; @@ -78,14 +81,15 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao status = theResource.getStatus(); List base = theResource.getBase(); + String code = theResource.getCode(); String expression = theResource.getExpression(); FhirContext context = getContext(); 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 theBase, String theExpression, FhirContext theContext, DaoConfig theDaoConfig) { + public static void validateSearchParam(ISearchParamRegistry theSearchParamRegistry, ISearchParamExtractor theSearchParamExtractor, String theCode, Enum theType, Enum theStatus, List theBase, String theExpression, FhirContext theContext, DaoConfig theDaoConfig) { if (theStatus == null) { throw new UnprocessableEntityException("SearchParameter.status is missing or invalid"); } @@ -146,6 +150,19 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao 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"); + } + } + } + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java index 2ff6366c092..cc6f8d02aa1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java @@ -68,11 +68,12 @@ public class FhirResourceDaoSearchParameterR5 extends BaseHapiFhirResourceDao status = theResource.getStatus(); List base = theResource.getBase(); + String code = theResource.getCode(); String expression = theResource.getExpression(); FhirContext context = getContext(); Enum type = theResource.getType(); - FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); + FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java index 56bec7d4391..b33feba256c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java @@ -2,27 +2,39 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.DaoConfig; 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.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.lang3.time.DateUtils; 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.AfterClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.PageRequest; import java.io.IOException; import java.util.Date; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.contains; +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 { private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class); @@ -31,6 +43,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { public void afterResetDao() { myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy()); + myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden()); } @Test @@ -149,7 +162,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { p = new Patient(); p.setActive(false); try { - myPatientDao.create(p).getId(); + myPatientDao.create(p); fail(); } catch (ResourceVersionConflictException e) { // 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 public static void afterClassClearContext() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java index 34f8f6c6d4b..d1aa5668a33 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java @@ -22,7 +22,6 @@ import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; -import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.*; @@ -32,10 +31,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatchers; -import org.springframework.aop.framework.AopProxyUtils; 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.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; @@ -48,8 +44,17 @@ import java.util.UUID; import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +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.Mockito.mock; import static org.mockito.Mockito.when; @@ -695,7 +700,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { assertEquals(1, myResourceReindexingSvc.forceReindexingPass()); assertEquals(0, myResourceReindexingSvc.forceReindexingPass()); - runInTransaction(()->{ + runInTransaction(() -> { List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); assertEquals(uniques.toString(), 1, uniques.size()); 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(); assertEquals(0, myResourceReindexingSvc.forceReindexingPass()); - runInTransaction(()->{ + runInTransaction(() -> { List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); assertEquals(uniques.toString(), 1, uniques.size()); assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart()))); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/MultitenantR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/MultitenantR4Test.java index f62bc29557e..d77a5e755d1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/MultitenantR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/MultitenantR4Test.java @@ -4,8 +4,10 @@ import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; 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.ResourceIndexedCompositeStringUnique; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceLink; 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.r4.model.BooleanType; 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.Patient; import org.hl7.fhir.r4.model.SearchParameter; @@ -29,13 +32,13 @@ import javax.servlet.ServletException; import java.time.LocalDate; import java.time.Month; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; 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 MyInterceptor myTenantInterceptor; + private LocalDate myTenantDate; + private int myTenantId; @After public void after() { @@ -58,6 +63,11 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest { super.before(); 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 public void testCreateResourceWithTenant() { createUniqueCompositeSp(); + createRequestId(); - addTenant(3, LocalDate.of(2020, Month.JANUARY, 14)); - addTenant(3, LocalDate.of(2020, Month.JANUARY, 14)); + addCreateTenant(myTenantId, myTenantDate); + addCreateTenant(myTenantId, myTenantDate); Organization org = new Organization(); org.setName("org"); @@ -91,75 +103,155 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest { p.addIdentifier().setSystem("system").setValue("value"); p.setBirthDate(new Date()); p.getManagingOrganization().setReferenceElement(orgId); - when(mySrd.getRequestId()).thenReturn("REQUEST_ID"); Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); runInTransaction(() -> { // HFJ_RESOURCE ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new); - assertEquals(3, resourceTable.getTenantId().getTenantId().intValue()); - assertEquals(LocalDate.of(2020, Month.JANUARY, 14), resourceTable.getTenantId().getTenantDate()); - - resourceTable.getProfile() + assertEquals(myTenantId, resourceTable.getTenantId().getTenantId().intValue()); + assertEquals(myTenantDate, resourceTable.getTenantId().getTenantDate()); // HFJ_RES_VER ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L); - assertEquals(3, version.getTenantId().getTenantId().intValue()); - assertEquals(LocalDate.of(2020, Month.JANUARY, 14), version.getTenantId().getTenantDate()); + assertEquals(myTenantId, version.getTenantId().getTenantId().intValue()); + 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 List strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId); ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * "))); assertEquals(10, strings.size()); - assertEquals(3, strings.get(0).getTenantId().getTenantId().intValue()); - assertEquals(LocalDate.of(2020, Month.JANUARY, 14), strings.get(0).getTenantId().getTenantDate()); + assertEquals(myTenantId, strings.get(0).getTenantId().getTenantId().intValue()); + assertEquals(myTenantDate, strings.get(0).getTenantId().getTenantDate()); + + // HFJ_SPIDX_DATE + List 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 List resourceLinks = myResourceLinkDao.findAllForResourceId(patientId); assertEquals(1, resourceLinks.size()); - assertEquals(3, resourceLinks.get(0).getTenantId().getTenantId().intValue()); - assertEquals(LocalDate.of(2020, Month.JANUARY, 14), resourceLinks.get(0).getTenantId().getTenantDate()); + assertEquals(myTenantId, resourceLinks.get(0).getTenantId().getTenantId().intValue()); + assertEquals(myTenantDate, resourceLinks.get(0).getTenantId().getTenantDate()); // HFJ_RES_PARAM_PRESENT List presents = mySearchParamPresentDao.findAllForResource(resourceTable); - assertEquals(3, presents.size()); - assertEquals(3, presents.get(0).getTenantId().getTenantId().intValue()); - assertEquals(LocalDate.of(2020, Month.JANUARY, 14), presents.get(0).getTenantId().getTenantDate()); + assertEquals(myTenantId, presents.size()); + assertEquals(myTenantId, presents.get(0).getTenantId().getTenantId().intValue()); + assertEquals(myTenantDate, presents.get(0).getTenantId().getTenantDate()); // HFJ_IDX_CMP_STRING_UNIQ List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId); - assertEquals(3, uniques.size()); - assertEquals(3, uniques.get(0).getTenantId().getTenantId().intValue()); - assertEquals(LocalDate.of(2020, Month.JANUARY, 14), uniques.get(0).getTenantId().getTenantDate()); + assertEquals(1, uniques.size()); + assertEquals(myTenantId, uniques.get(0).getTenantId().getTenantId().intValue()); + 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 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 public void testUpdateResourceWithTenant() { - createUniqueCompositeSp(); - - addTenant(3, LocalDate.of(2020, Month.JANUARY, 14)); + createRequestId(); + addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14)); + // Create a resource Patient p = new Patient(); p.setActive(true); 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.setId("Patient/" + patientId); p.setActive(false); - myPatientDao.update(p); + myPatientDao.update(p, mySrd); 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 - ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 2); - assertEquals(tenantId, resVer.getTenantId().getTenantId().intValue()); - assertEquals(tenantDate, resVer.getTenantId().getTenantDate()); + int version = 2; + ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, version); + 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 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() { SearchParameter sp = new SearchParameter(); sp.setId("SearchParameter/patient-birthdate"); @@ -171,7 +263,7 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest { mySearchParameterDao.update(sp); sp = new SearchParameter(); - sp.setId("SearchParameter/patient-birthdate"); + sp.setId("SearchParameter/patient-birthdate-unique"); sp.setType(Enumerations.SearchParamType.COMPOSITE); sp.setStatus(Enumerations.PublicationStatus.ACTIVE); sp.addBase("Patient"); @@ -186,27 +278,48 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest { mySearchParamRegistry.forceRefresh(); } - public void addTenant(int theTenantId, LocalDate theTenantDate) { + + + private void addCreateTenant(int theTenantId, LocalDate theTenantDate) { if (myTenantInterceptor == null) { 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... theModifiers) { + addCreateTenant(theTenantId, null); + Patient p = new Patient(); + for (Consumer next : theModifiers) { + next.accept(p); + } + + return myPatientDao.create(p).getId().toUnqualifiedVersionless(); + } + + public void createRequestId() { + when(mySrd.getRequestId()).thenReturn("REQUEST_ID"); + } + + private Consumer withActiveTrue() { + return t->t.setActive(true); } @Interceptor public static class MyInterceptor { - private final List myTenantIds = new ArrayList<>(); - public void addTenant(TenantId theTenantId) { + private final List myCreateTenantIds = new ArrayList<>(); + + public void addCreateTenant(TenantId theTenantId) { Validate.notNull(theTenantId); - myTenantIds.add(theTenantId); + myCreateTenantIds.add(theTenantId); } @Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE) public TenantId tenantIdentifyCreate() { - TenantId retVal = myTenantIds.remove(0); + TenantId retVal = myCreateTenantIds.remove(0); ourLog.info("Returning tenant ID: {}", retVal); return retVal; } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 057c28aaafd..814f36c553c 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -70,7 +70,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { 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"); + version.onTable("HFJ_RESOURCE").dropIndex("20200327.1", "IDX_RES_PROFILE"); + version.onTable("HFJ_RESOURCE").dropColumn("20200327.2", "RES_PROFILE"); } protected void init420() { // 20191015 - 20200217 diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ForcedId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ForcedId.java index 720163bec12..7807d918ecb 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ForcedId.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ForcedId.java @@ -62,6 +62,8 @@ public class ForcedId { @ColumnDefault("''") @Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true) private String myResourceType; + @Embedded + private TenantId myTenantId; /** * Constructor @@ -93,4 +95,12 @@ public class ForcedId { public Long getId() { return myId; } + + public TenantId getTenantId() { + return myTenantId; + } + + public void setTenantId(TenantId theTenantId) { + myTenantId = theTenantId; + } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java index 200585874e6..b286d68990e 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java @@ -51,18 +51,17 @@ public class ResourceHistoryProvenanceEntity { @Embedded private TenantId myTenantId; - public ResourceTable getResourceTable() { - return myResourceTable; + /** + * Constructor + */ + public ResourceHistoryProvenanceEntity() { + super(); } public void setResourceTable(ResourceTable theResourceTable) { myResourceTable = theResourceTable; } - public ResourceHistoryTable getResourceHistoryTable() { - return myResourceHistoryTable; - } - public void setResourceHistoryTable(ResourceHistoryTable theResourceHistoryTable) { myResourceHistoryTable = theResourceHistoryTable; } @@ -86,4 +85,12 @@ public class ResourceHistoryProvenanceEntity { public Long getId() { return myId; } + + public TenantId getTenantId() { + return myTenantId; + } + + public void setTenantId(TenantId theTenantId) { + myTenantId = theTenantId; + } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedCompositeStringUnique.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedCompositeStringUnique.java index 27870d6e608..ee7aafa2fc2 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedCompositeStringUnique.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedCompositeStringUnique.java @@ -64,6 +64,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable