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 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) {
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