From b2d2346228a8d1fcaa17afbf8e2932da661b4815 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Fri, 7 Feb 2020 15:18:06 -0500
Subject: [PATCH 01/63] Work on multitenancy
---
.../java/ca/uhn/fhir/util/VersionEnum.java | 3 +-
.../hapi/fhir/changelog/4_3_0/changes.yaml | 13 ++
hapi-fhir-jpaserver-base/pom.xml | 53 ++-----
.../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 34 ++++-
.../fhir/jpa/dao/r4/MultitenantR4Test.java | 130 ++++++++++++++++++
.../tasks/HapiFhirJpaMigrationTasks.java | 8 +-
.../jpa/model/entity/BaseHasResource.java | 12 +-
.../BaseResourceIndexedSearchParam.java | 29 +++-
.../fhir/jpa/model/entity/ResourceTable.java | 33 ++---
.../fhir/jpa/model/entity/ResourceTag.java | 36 +++--
.../uhn/fhir/jpa/model/entity/TenantId.java | 39 ++++++
.../uhn/fhir/jpa/model/util/JpaConstants.java | 55 ++------
pom.xml | 7 +-
13 files changed, 317 insertions(+), 135 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/MultitenantR4Test.java
create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
index acc92dcb2a6..99d8ccd7939 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
@@ -58,7 +58,8 @@ public enum VersionEnum {
V4_0_0,
V4_0_3,
V4_1_0,
- V4_2_0;
+ V4_2_0,
+ V4_3_0;
public static VersionEnum latestVersion() {
VersionEnum[] values = VersionEnum.values();
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
new file mode 100644
index 00000000000..fd6d640f6b3
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
@@ -0,0 +1,13 @@
+---
+- item:
+ type: "add"
+ title: "The version of a few dependencies have been bumped to the latest versions
+ (dependent HAPI modules listed in brackets):
+
+ Hibernate ORM (JPA): 5.4.6 -> 5.4.10
+ "
+- item:
+ type: change
+ title: "The JPA server now shared a single set of tags for all versions of a resource, bringing it in line with the
+ functional description in the FHIR specification. This means that it is no longer possible to modify the tags for
+ a specific version of a resource, and also causes significant performance improvements in some cases."
diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml
index 73c106c90e2..3e94f6f8eda 100644
--- a/hapi-fhir-jpaserver-base/pom.xml
+++ b/hapi-fhir-jpaserver-base/pom.xml
@@ -236,18 +236,6 @@
javax.annotation
javax.annotation-api
-
-
-
-
-
@@ -255,11 +243,6 @@
derby
test
-
org.apache.derby
derbytools
@@ -319,15 +302,7 @@
-
-
-
org.springframework
spring-core
@@ -370,12 +345,6 @@
org.springframework
spring-messaging
-
org.springframework
spring-tx
@@ -422,6 +391,16 @@
+
+ org.hibernate
+ hibernate-java8
+
+
+ org.jboss.spec.javax.transaction
+ jboss-transaction-api_1.2_spec
+
+
+
org.hibernate
hibernate-ehcache
@@ -451,10 +430,6 @@
javax.activation
1.2.0
-
-
-
-
javax.mail
javax.mail-api
@@ -471,14 +446,6 @@
-
-
-
-
-
javax.el
javax.el-api
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 fa4b228bb78..81940910cba 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
@@ -1,10 +1,21 @@
package ca.uhn.fhir.jpa.dao;
-import ca.uhn.fhir.context.*;
+import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
-import ca.uhn.fhir.jpa.dao.data.*;
+import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
+import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
+import ca.uhn.fhir.jpa.dao.data.IResourceProvenanceDao;
+import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
+import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
@@ -75,7 +86,11 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.PostConstruct;
-import javax.persistence.*;
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.PersistenceContext;
+import javax.persistence.PersistenceContextType;
+import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
@@ -84,7 +99,12 @@ import javax.xml.stream.events.XMLEvent;
import java.util.*;
import java.util.Map.Entry;
-import static org.apache.commons.lang3.StringUtils.*;
+import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
+import static org.apache.commons.lang3.StringUtils.defaultString;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.left;
+import static org.apache.commons.lang3.StringUtils.trim;
/*
* #%L
@@ -149,6 +169,8 @@ public abstract class BaseHapiFhirDao extends BaseStora
@Autowired
protected IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
+ protected DaoRegistry myDaoRegistry;
+ @Autowired
ExpungeService myExpungeService;
@Autowired
private DaoConfig myConfig;
@@ -159,8 +181,6 @@ public abstract class BaseHapiFhirDao extends BaseStora
@Autowired
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
@Autowired
- protected DaoRegistry myDaoRegistry;
- @Autowired
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
@Autowired
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
@@ -620,6 +640,7 @@ public abstract class BaseHapiFhirDao extends BaseStora
}
}
+
return retVal;
}
@@ -681,6 +702,7 @@ public abstract class BaseHapiFhirDao extends BaseStora
}
}
}
+
return retVal;
}
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
new file mode 100644
index 00000000000..bc68bfaef01
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/MultitenantR4Test.java
@@ -0,0 +1,130 @@
+package ca.uhn.fhir.jpa.dao.r4;
+
+import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
+import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceTag;
+import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
+import ca.uhn.fhir.jpa.model.util.JpaConstants;
+import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.ReferenceParam;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
+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.io.IOUtils;
+import org.hamcrest.Matchers;
+import org.hl7.fhir.instance.model.api.IAnyResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.*;
+import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
+import org.hl7.fhir.r4.model.Bundle.BundleEntryRequestComponent;
+import org.hl7.fhir.r4.model.Bundle.BundleEntryResponseComponent;
+import org.hl7.fhir.r4.model.Bundle.BundleType;
+import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
+import org.hl7.fhir.r4.model.Observation.ObservationStatus;
+import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class MultitenantR4Test extends BaseJpaR4SystemTest {
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MultitenantR4Test.class);
+
+ @After
+ public void after() {
+ }
+
+ @Before
+ public void beforeDisableResultReuse() {
+ }
+
+
+ @Test
+ public void testCreateResourceNoTenant() {
+ Patient p = new Patient();
+ p.addIdentifier().setSystem("system").setValue("value");
+ p.setBirthDate(new Date());
+ Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
+
+ runInTransaction(()->{
+ ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(() -> new IllegalArgumentException());
+ assertNull(resourceTable.getTenantId());
+ });
+ }
+
+ @Test
+ public void testCreateResourceWithTenant() {
+ Patient p = new Patient();
+ p.setUserData(JpaConstants.USERDATA_TENANT_ID, 3);
+ p.setUserData(JpaConstants.USERDATA_TENANT_DATE, LocalDate.of(2020, Month.JANUARY, 14));
+ p.addIdentifier().setSystem("system").setValue("value");
+ p.setBirthDate(new Date());
+ Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
+
+ runInTransaction(()->{
+ ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(() -> new IllegalArgumentException());
+ assertNull(resourceTable.getTenantId());
+ });
+
+ }
+
+ @AfterClass
+ public static void afterClassClearContext() {
+ TestUtil.clearAllStaticFieldsForUnitTest();
+ }
+
+}
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 90e920e1234..41a430a2bc4 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
@@ -56,7 +56,13 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
init360(); // 20180918 - 20181112
init400(); // 20190401 - 20190814
init410(); // 20190815 - 20191014
- init420(); // 20191015 - present
+ init420(); // 20191015 - 20200213
+ init430(); // 20200213 - present
+ }
+
+ protected void init430() {
+ Builder version = forVersion(VersionEnum.V4_3_0);
+
}
protected void init420() { // 20191015 - present
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java
index 6970b64dc68..71ad0e10e80 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java
@@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
-import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.Constants;
@@ -64,6 +63,9 @@ public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersi
@OptimisticLock(excluded = true)
private Date myUpdated;
+ @Embedded
+ private TenantId myTenantId;
+
/**
* This is stored as an optimization to avoid neeind to query for this
* after an update
@@ -71,6 +73,14 @@ public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersi
@Transient
private transient String myTransientForcedId;
+ public TenantId getTenantId() {
+ return myTenantId;
+ }
+
+ public void setTenantId(TenantId theTenantId) {
+ myTenantId = theTenantId;
+ }
+
public String getTransientForcedId() {
return myTransientForcedId;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
index b891a3889b2..b88d63e6cb1 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
@@ -31,7 +31,14 @@ import com.google.common.hash.Hashing;
import org.hibernate.search.annotations.ContainedIn;
import org.hibernate.search.annotations.Field;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
import java.util.Date;
@MappedSuperclass
@@ -47,10 +54,9 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8);
private static final long serialVersionUID = 1L;
- // TODO: make this nullable=false and a primitive (written may 2017)
@Field()
- @Column(name = "SP_MISSING", nullable = true)
- private Boolean myMissing = Boolean.FALSE;
+ @Column(name = "SP_MISSING", nullable = false)
+ private boolean myMissing = false;
@Field
@Column(name = "SP_NAME", length = MAX_SP_NAME, nullable = false)
@@ -64,14 +70,27 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
@Column(name = "RES_ID", insertable = false, updatable = false, nullable = false)
private Long myResourcePid;
+ // FIXME: replace with join
@Field()
@Column(name = "RES_TYPE", nullable = false, length = Constants.MAX_RESOURCE_NAME_LENGTH)
private String myResourceType;
+
@Field()
@Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
@Temporal(TemporalType.TIMESTAMP)
private Date myUpdated;
+ @Embedded
+ private TenantId myTenantId;
+
+ public TenantId getTenantId() {
+ return myTenantId;
+ }
+
+ public void setTenantId(TenantId theTenantId) {
+ myTenantId = theTenantId;
+ }
+
/**
* Subclasses may override
*/
@@ -123,7 +142,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
}
public boolean isMissing() {
- return Boolean.TRUE.equals(myMissing);
+ return myMissing;
}
public BaseResourceIndexedSearchParam setMissing(boolean theMissing) {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
index b46c9d9cd6c..cf6511f28ab 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
@@ -27,12 +27,20 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.annotations.OptimisticLock;
-import org.hibernate.search.annotations.*;
+import org.hibernate.search.annotations.Analyze;
+import org.hibernate.search.annotations.Analyzer;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Fields;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Store;
-import javax.persistence.Index;
import javax.persistence.*;
import java.io.Serializable;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
@@ -52,12 +60,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
private static final int MAX_PROFILE_LENGTH = 200;
private static final long serialVersionUID = 1L;
-// @Transient
-// private transient byte[] myResource;
-//
-// @Transient
-// private transient ResourceEncodingEnum myEncoding;
-
/**
* Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
*/
@@ -186,9 +188,9 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
* {@link #myHasLinks} is true, meaning that there are actually resource links present
* right now. This avoids Hibernate Search triggering a select on the resource link
* table.
- *
+ *
* This field is used by FulltextSearchSvcImpl
- *
+ *
* You can test that any changes don't cause extra queries by running
* FhirResourceDaoR4QueryCountTest
*/
@@ -421,7 +423,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
return this;
}
- @Override
public Collection getTags() {
if (myTags == null) {
myTags = new HashSet<>();
@@ -590,16 +591,16 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
* This is a convenience to avoid loading the version a second time within a single transaction. It is
* not persisted.
*/
- public void setCurrentVersionEntity(ResourceHistoryTable theCurrentVersionEntity) {
- myCurrentVersionEntity = theCurrentVersionEntity;
+ public ResourceHistoryTable getCurrentVersionEntity() {
+ return myCurrentVersionEntity;
}
/**
* This is a convenience to avoid loading the version a second time within a single transaction. It is
* not persisted.
*/
- public ResourceHistoryTable getCurrentVersionEntity() {
- return myCurrentVersionEntity;
+ public void setCurrentVersionEntity(ResourceHistoryTable theCurrentVersionEntity) {
+ myCurrentVersionEntity = theCurrentVersionEntity;
}
@Override
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java
index 9fcb5200ce4..7d06439837c 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java
@@ -28,8 +28,8 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.*;
@Entity
-@Table(name = "HFJ_RES_TAG", uniqueConstraints= {
- @UniqueConstraint(name="IDX_RESTAG_TAGID", columnNames= {"RES_ID","TAG_ID"})
+@Table(name = "HFJ_RES_TAG", uniqueConstraints = {
+ @UniqueConstraint(name = "IDX_RESTAG_TAGID", columnNames = {"RES_ID", "TAG_ID"})
})
public class ResourceTag extends BaseTag {
@@ -42,7 +42,7 @@ public class ResourceTag extends BaseTag {
private Long myId;
@ManyToOne(cascade = {})
- @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey=@ForeignKey(name="FK_RESTAG_RESOURCE"))
+ @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_RESTAG_RESOURCE"))
private ResourceTable myResource;
@Column(name = "RES_TYPE", length = ResourceTable.RESTYPE_LEN, nullable = false)
@@ -50,14 +50,8 @@ public class ResourceTag extends BaseTag {
@Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourceId;
-
- public Long getResourceId() {
- return myResourceId;
- }
-
- public void setResourceId(Long theResourceId) {
- myResourceId = theResourceId;
- }
+ @Embedded
+ private TenantId myTenantId;
public ResourceTag() {
}
@@ -69,18 +63,30 @@ public class ResourceTag extends BaseTag {
setResourceType(theResourceTable.getResourceType());
}
+ public void setTenantId(TenantId theTenantId) {
+ myTenantId = theTenantId;
+ }
+
+ public Long getResourceId() {
+ return myResourceId;
+ }
+
+ public void setResourceId(Long theResourceId) {
+ myResourceId = theResourceId;
+ }
+
public ResourceTable getResource() {
return myResource;
}
- public String getResourceType() {
- return myResourceType;
- }
-
public void setResource(ResourceTable theResource) {
myResource = theResource;
}
+ public String getResourceType() {
+ return myResourceType;
+ }
+
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java
new file mode 100644
index 00000000000..3143e8559cf
--- /dev/null
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java
@@ -0,0 +1,39 @@
+package ca.uhn.fhir.jpa.model.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import java.time.LocalDate;
+
+@Embeddable
+public class TenantId implements Cloneable {
+
+ @Column(name = "TENANT_ID", nullable = true)
+ private Integer myTenantId;
+ @Column(name = "TENANT_DATE", nullable = true)
+ private LocalDate myTenantDate;
+
+ public Integer getTenantId() {
+ return myTenantId;
+ }
+
+ public TenantId setTenantId(Integer theTenantId) {
+ myTenantId = theTenantId;
+ return this;
+ }
+
+ public LocalDate getTenantDate() {
+ return myTenantDate;
+ }
+
+ public TenantId setTenantDate(LocalDate theTenantDate) {
+ myTenantDate = theTenantDate;
+ return this;
+ }
+
+ @Override
+ protected TenantId clone() {
+ return new TenantId()
+ .setTenantId(getTenantId())
+ .setTenantDate(getTenantDate());
+ }
+}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
index 66f2c5b17ed..d5a9a547828 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
@@ -24,39 +24,30 @@ import ca.uhn.fhir.rest.api.Constants;
public class JpaConstants {
- /**
- * Non-instantiable
- */
- private JpaConstants() {
- // nothing
- }
+ public static final String USERDATA_TENANT_ID = JpaConstants.class.getName() + "_USERDATA_TENANT_ID";
+ public static final String USERDATA_TENANT_DATE = JpaConstants.class.getName() + "_USERDATA_TENANT_DATE";
/**
* Operation name for the $apply-codesystem-delta-add operation
*/
public static final String OPERATION_APPLY_CODESYSTEM_DELTA_ADD = "$apply-codesystem-delta-add";
-
/**
* Operation name for the $apply-codesystem-delta-remove operation
*/
public static final String OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE = "$apply-codesystem-delta-remove";
-
/**
* Operation name for the $expunge operation
*/
public static final String OPERATION_EXPUNGE = "$expunge";
-
/**
* Operation name for the $match operation
*/
public static final String OPERATION_MATCH = "$match";
-
/**
* @deprecated Replace with {@link #OPERATION_EXPUNGE}
*/
@Deprecated
public static final String OPERATION_NAME_EXPUNGE = OPERATION_EXPUNGE;
-
/**
* Parameter name for the $expunge operation
*/
@@ -84,113 +75,91 @@ public class JpaConstants {
* be removed if they are nt explicitly included in updates
*/
public static final String HEADER_META_SNAPSHOT_MODE = "X-Meta-Snapshot-Mode";
-
/**
* Operation name for the $lookup operation
*/
public static final String OPERATION_LOOKUP = "$lookup";
-
/**
* Operation name for the $expand operation
*/
public static final String OPERATION_EXPAND = "$expand";
-
/**
* Operation name for the $validate-code operation
*/
public static final String OPERATION_VALIDATE_CODE = "$validate-code";
-
/**
* Operation name for the $get-resource-counts operation
*/
public static final String OPERATION_GET_RESOURCE_COUNTS = "$get-resource-counts";
-
/**
* Operation name for the $meta operation
*/
public static final String OPERATION_META = "$meta";
-
/**
* Operation name for the $validate operation
*/
// NB don't delete this, it's used in Smile as well, even though hapi-fhir-server uses the version from Constants.java
public static final String OPERATION_VALIDATE = Constants.EXTOP_VALIDATE;
-
/**
* Operation name for the $suggest-keywords operation
*/
public static final String OPERATION_SUGGEST_KEYWORDS = "$suggest-keywords";
-
/**
* Operation name for the $everything operation
*/
public static final String OPERATION_EVERYTHING = "$everything";
-
/**
* Operation name for the $process-message operation
*/
public static final String OPERATION_PROCESS_MESSAGE = "$process-message";
-
/**
* Operation name for the $meta-delete operation
*/
public static final String OPERATION_META_DELETE = "$meta-delete";
-
/**
* Operation name for the $meta-add operation
*/
public static final String OPERATION_META_ADD = "$meta-add";
-
/**
* Operation name for the $translate operation
*/
public static final String OPERATION_TRANSLATE = "$translate";
-
/**
* Operation name for the $document operation
*/
public static final String OPERATION_DOCUMENT = "$document";
-
/**
* Trigger a subscription manually for a given resource
*/
public static final String OPERATION_TRIGGER_SUBSCRIPTION = "$trigger-subscription";
-
/**
* Operation name for the "$subsumes" operation
*/
public static final String OPERATION_SUBSUMES = "$subsumes";
-
/**
* Operation name for the "$snapshot" operation
*/
public static final String OPERATION_SNAPSHOT = "$snapshot";
-
/**
* Operation name for the "$binary-access" operation
*/
public static final String OPERATION_BINARY_ACCESS_READ = "$binary-access-read";
-
/**
* Operation name for the "$binary-access" operation
*/
public static final String OPERATION_BINARY_ACCESS_WRITE = "$binary-access-write";
-
/**
* Operation name for the "$upload-external-code-system" operation
*/
public static final String OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM = "$upload-external-code-system";
-
/**
* Operation name for the "$export" operation
*/
public static final String OPERATION_EXPORT = "$export";
-
/**
* Operation name for the "$export-poll-status" operation
*/
public static final String OPERATION_EXPORT_POLL_STATUS = "$export-poll-status";
-
/**
*
* This extension should be of type string
and should be
@@ -198,7 +167,6 @@ public class JpaConstants {
*
*/
public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template";
-
/**
* This extension URL indicates whether a REST HOOK delivery should
* include the version ID when delivering.
@@ -208,7 +176,6 @@ public class JpaConstants {
*
*/
public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids";
-
/**
* This extension URL indicates whether a REST HOOK delivery should
* reload the resource and deliver the latest version always. This
@@ -226,12 +193,10 @@ public class JpaConstants {
*
*/
public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
-
/**
* Indicate which strategy will be used to match this subscription
*/
public static final String EXT_SUBSCRIPTION_MATCHING_STRATEGY = "http://hapifhir.io/fhir/StructureDefinition/subscription-matching-strategy";
-
/**
*
* This extension should be of type string
and should be
@@ -239,45 +204,43 @@ public class JpaConstants {
*
*/
public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
-
/**
* Extension ID for external binary references
*/
public static final String EXT_EXTERNALIZED_BINARY_ID = "http://hapifhir.io/fhir/StructureDefinition/externalized-binary-id";
-
/**
* Placed in system-generated extensions
*/
public static final String EXTENSION_EXT_SYSTEMDEFINED = JpaConstants.class.getName() + "_EXTENSION_EXT_SYSTEMDEFINED";
-
/**
* Message added to expansion valueset
*/
public static final String EXT_VALUESET_EXPANSION_MESSAGE = "http://hapifhir.io/fhir/StructureDefinition/valueset-expansion-message";
-
-
/**
* Parameter for the $export operation
*/
public static final String PARAM_EXPORT_POLL_STATUS_JOB_ID = "_jobId";
-
/**
* Parameter for the $export operation
*/
public static final String PARAM_EXPORT_OUTPUT_FORMAT = "_outputFormat";
-
/**
* Parameter for the $export operation
*/
public static final String PARAM_EXPORT_TYPE = "_type";
-
/**
* Parameter for the $export operation
*/
public static final String PARAM_EXPORT_SINCE = "_since";
-
/**
* Parameter for the $export operation
*/
public static final String PARAM_EXPORT_TYPE_FILTER = "_typeFilter";
+
+ /**
+ * Non-instantiable
+ */
+ private JpaConstants() {
+ // nothing
+ }
}
diff --git a/pom.xml b/pom.xml
index 1371e1b3c8a..2ef99fd2479 100644
--- a/pom.xml
+++ b/pom.xml
@@ -645,7 +645,7 @@
3.0.2
6.1.0
- 5.4.6.Final
+ 5.4.10.Final
5.11.3.Final
5.5.5
@@ -1278,6 +1278,11 @@
hibernate-core
${hibernate_version}
+
+ org.hibernate
+ hibernate-java8
+ ${hibernate_version}
+
org.hibernate
hibernate-ehcache
From f7ec41ffc5e03aa1abc127950ccd830bd8a8ac3d Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Sat, 8 Feb 2020 19:59:37 -0500
Subject: [PATCH 02/63] Work on multitenancy
---
.../interceptor/api/IInterceptorService.java | 5 +
.../ca/uhn/fhir/interceptor/api/Pointcut.java | 37 ++++++
.../executor/InterceptorService.java | 17 +++
.../hapi/fhir/changelog/4_3_0/changes.yaml | 2 +-
.../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 15 ++-
.../ca/uhn/fhir/jpa/dao/BaseStorageDao.java | 5 +-
.../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 25 +++-
.../dao/index/DaoSearchParamSynchronizer.java | 1 +
.../fhir/jpa/dao/r4/MultitenantR4Test.java | 123 ++++++++----------
.../jpa/model/entity/BaseResourceIndex.java | 14 ++
.../BaseResourceIndexedSearchParam.java | 11 --
.../fhir/jpa/model/entity/ResourceTable.java | 9 ++
.../uhn/fhir/jpa/model/entity/TenantId.java | 28 +++-
.../uhn/fhir/jpa/model/util/JpaConstants.java | 3 -
.../uhn/fhirtest/config/TestDstu2Config.java | 2 +
.../uhn/fhirtest/config/TestDstu3Config.java | 2 +
.../ca/uhn/fhirtest/config/TestR4Config.java | 2 +
.../ca/uhn/fhirtest/config/TestR5Config.java | 2 +
pom.xml | 4 +-
19 files changed, 216 insertions(+), 91 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IInterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IInterceptorService.java
index 04a6d513bbc..c4655ce8acb 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IInterceptorService.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IInterceptorService.java
@@ -23,6 +23,7 @@ package ca.uhn.fhir.interceptor.api;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
+import java.util.function.Function;
public interface IInterceptorService extends IInterceptorBroadcaster {
@@ -90,4 +91,8 @@ public interface IInterceptorService extends IInterceptorBroadcaster {
void registerInterceptors(@Nullable Collection> theInterceptors);
+ /**
+ * Unregisters all interceptors that are indicated by the given callback function returning true
+ */
+ void unregisterInterceptorsIf(Function theShouldUnregisterFunction);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
index 634ccade76e..e569f7ca872 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
@@ -1321,6 +1321,43 @@ public enum Pointcut {
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
),
+ /**
+ * Storage Hook:
+ * Invoked before an $expunge
operation on all data (expungeEverything) is called.
+ *
+ * Hooks will be passed a reference to a counter containing the current number of records that have been deleted.
+ * If the hook deletes any records, the hook is expected to increment this counter by the number of records deleted.
+ *
+ * Hooks may accept the following parameters:
+ *
+ * org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned.
+ *
+ * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
+ * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
+ * pulled out of the servlet request. Note that the bean
+ * properties are not all guaranteed to be populated, depending on how early during processing the
+ * exception occurred.
+ *
+ *
+ * ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
+ * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
+ * pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
+ * only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
+ *
+ *
+ *
+ * Hooks should return an instance of ca.uhn.fhir.jpa.model.entity.TenantId
or null
.
+ *
+ */
+ STORAGE_TENANT_IDENTIFY_CREATE (
+ // Return type
+ "ca.uhn.fhir.jpa.model.entity.TenantId",
+ // Params
+ "org.hl7.fhir.instance.model.api.IBaseResource",
+ "ca.uhn.fhir.rest.api.server.RequestDetails",
+ "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
+ ),
+
/**
* Performance Tracing Hook:
* This hook is invoked when any informational messages generated by the
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java
index 0c01735b266..25ff3fed920 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java
@@ -41,6 +41,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
import java.util.stream.Collectors;
public class InterceptorService implements IInterceptorService, IInterceptorBroadcaster {
@@ -145,6 +146,22 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
}
}
+ @Override
+ public void unregisterInterceptorsIf(Function theShouldUnregisterFunction) {
+ unregisterInterceptorsIf(theShouldUnregisterFunction, myGlobalInvokers);
+ unregisterInterceptorsIf(theShouldUnregisterFunction, myAnonymousInvokers);
+ }
+
+ private void unregisterInterceptorsIf(Function theShouldUnregisterFunction, ListMultimap theGlobalInvokers) {
+ for (Iterator> iter = theGlobalInvokers.entries().iterator(); iter.hasNext(); ) {
+ Map.Entry next = iter.next();
+ Object nextInterceptor = next.getValue().getInterceptor();
+ if (theShouldUnregisterFunction.apply(nextInterceptor)) {
+ iter.remove();
+ }
+ }
+ }
+
@Override
public boolean registerThreadLocalInterceptor(Object theInterceptor) {
if (!myThreadlocalInvokersEnabled) {
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
index fd6d640f6b3..a763784c8b7 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
@@ -4,7 +4,7 @@
title: "The version of a few dependencies have been bumped to the latest versions
(dependent HAPI modules listed in brackets):
- Hibernate ORM (JPA): 5.4.6 -> 5.4.10
+ Hibernate ORM (JPA): 5.4.6.Final -> 5.4.11.Final
"
- item:
type: change
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
index 8a5bfe6f555..8b2a70ea717 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
@@ -390,6 +390,19 @@ public abstract class BaseHapiFhirResourceDao extends B
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
+ if (myDaoConfig.isMultiTenancyEnabled()) {
+ // Interceptor call: STORAGE_TENANT_IDENTIFY_CREATE
+ HookParams params = new HookParams()
+ .add(IBaseResource.class, theResource)
+ .add(RequestDetails.class, theRequest)
+ .addIfMatchesType(ServletRequestDetails.class, theRequest);
+ TenantId tenantId = (TenantId) doCallHooksAndReturnObject(theRequest, Pointcut.STORAGE_TENANT_IDENTIFY_CREATE, params);
+ if (tenantId != null) {
+ ourLog.debug("Resource has been assigned tenant ID: {}", tenantId);
+ entity.setTenantId(tenantId);
+ }
+ }
+
if (isNotBlank(theIfNoneExist)) {
Set match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theRequest);
if (match.size() > 1) {
@@ -432,7 +445,7 @@ public abstract class BaseHapiFhirResourceDao extends B
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
}
- // Notify JPA interceptors
+ // Interceptor call: STORAGE_PRESTORAGE_RESOURCE_CREATED
HookParams hookParams = new HookParams()
.add(IBaseResource.class, theResource)
.add(RequestDetails.class, theRequest)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java
index b845ea07598..f996a9936cc 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java
@@ -54,7 +54,6 @@ import java.util.Set;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_ERROR;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_INFO;
import static org.apache.commons.lang3.StringUtils.defaultString;
-import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseStorageDao {
@@ -161,6 +160,10 @@ public abstract class BaseStorageDao {
JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequestDetails, thePointcut, theParams);
}
+ protected Object doCallHooksAndReturnObject(RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) {
+ return JpaInterceptorBroadcaster.doCallHooksAndReturnObject(getInterceptorBroadcaster(), theRequestDetails, thePointcut, theParams);
+ }
+
protected abstract IInterceptorBroadcaster getInterceptorBroadcaster();
public IBaseOperationOutcome createErrorOperationOutcome(String theMessage, String theCode) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
index 6168d8fe5e4..2f5936b3c0e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
@@ -183,6 +183,11 @@ public class DaoConfig {
*/
private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets;
+ /**
+ * @since 4.3.0
+ */
+ private boolean myMultiTenancyEnabled;
+
/**
* Constructor
*/
@@ -1907,7 +1912,25 @@ public class DaoConfig {
setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount()));
}
- public enum StoreMetaSourceInformationEnum {
+ /**
+ * If enabled (default is false
) the JPA server will support multitenant queries
+ *
+ * @since 4.3.0
+ */
+ public void setMultiTenancyEnabled(boolean theMultiTenancyEnabled) {
+ myMultiTenancyEnabled = theMultiTenancyEnabled;
+ }
+
+ /**
+ * If enabled (default is false
) the JPA server will support multitenant queries
+ *
+ * @since 4.3.0
+ */
+ public boolean isMultiTenancyEnabled() {
+ return myMultiTenancyEnabled;
+ }
+
+ public enum StoreMetaSourceInformationEnum {
NONE(false, false),
SOURCE_URI(true, false),
REQUEST_ID(false, true),
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
index a5b96de34b1..54a5e373e84 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
@@ -70,6 +70,7 @@ public class DaoSearchParamSynchronizer {
theEntity.getParamsQuantity().remove(next);
}
for (T next : quantitiesToAdd) {
+ next.setTenantId(theEntity.getTenantId());
myEntityManager.merge(next);
}
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 bc68bfaef01..23597b1e688 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
@@ -1,84 +1,31 @@
package ca.uhn.fhir.jpa.dao.r4;
-import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
+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.ResourceEncodingEnum;
-import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
-import ca.uhn.fhir.jpa.model.entity.ResourceTag;
-import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
+import ca.uhn.fhir.jpa.model.entity.TenantId;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
-import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
-import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.rest.api.Constants;
-import ca.uhn.fhir.rest.api.server.IBundleProvider;
-import ca.uhn.fhir.rest.param.ReferenceParam;
-import ca.uhn.fhir.rest.param.StringParam;
-import ca.uhn.fhir.rest.param.TokenParam;
-import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
-import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
-import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
-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.io.IOUtils;
-import org.hamcrest.Matchers;
-import org.hl7.fhir.instance.model.api.IAnyResource;
-import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.*;
-import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
-import org.hl7.fhir.r4.model.Bundle.BundleEntryRequestComponent;
-import org.hl7.fhir.r4.model.Bundle.BundleEntryResponseComponent;
-import org.hl7.fhir.r4.model.Bundle.BundleType;
-import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
-import org.hl7.fhir.r4.model.Observation.ObservationStatus;
-import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
+import org.apache.commons.lang3.Validate;
+import org.hl7.fhir.r4.model.Patient;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
-import org.springframework.transaction.TransactionDefinition;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.TransactionCallback;
-import org.springframework.transaction.support.TransactionCallbackWithoutResult;
-import org.springframework.transaction.support.TransactionTemplate;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.math.BigDecimal;
-import java.nio.charset.StandardCharsets;
+import javax.servlet.ServletException;
import java.time.LocalDate;
import java.time.Month;
+import java.util.Collections;
import java.util.Date;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
import java.util.stream.Collectors;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.emptyString;
-import static org.hamcrest.Matchers.endsWith;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.matchesPattern;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.startsWith;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
public class MultitenantR4Test extends BaseJpaR4SystemTest {
@@ -86,10 +33,17 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
@After
public void after() {
+ myDaoConfig.setMultiTenancyEnabled(new DaoConfig().isMultiTenancyEnabled());
+
+ myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
}
+ @Override
@Before
- public void beforeDisableResultReuse() {
+ public void before() throws ServletException {
+ super.before();
+
+ myDaoConfig.setMultiTenancyEnabled(true);
}
@@ -100,28 +54,57 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
p.setBirthDate(new Date());
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
- runInTransaction(()->{
- ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(() -> new IllegalArgumentException());
+ runInTransaction(() -> {
+ ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertNull(resourceTable.getTenantId());
});
}
@Test
public void testCreateResourceWithTenant() {
+ int expectId = 3;
+ LocalDate expectDate = LocalDate.of(2020, Month.JANUARY, 14);
+ myInterceptorRegistry.registerInterceptor(new MyInterceptor(new TenantId(expectId, expectDate)));
+
Patient p = new Patient();
- p.setUserData(JpaConstants.USERDATA_TENANT_ID, 3);
- p.setUserData(JpaConstants.USERDATA_TENANT_DATE, LocalDate.of(2020, Month.JANUARY, 14));
+ p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
- runInTransaction(()->{
- ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(() -> new IllegalArgumentException());
- assertNull(resourceTable.getTenantId());
+ runInTransaction(() -> {
+ ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
+ assertEquals(expectId, resourceTable.getTenantId().getTenantId().intValue());
+ assertEquals(expectDate, resourceTable.getTenantId().getTenantDate());
+
+ List strings = myResourceIndexedSearchParamStringDao.findAll();
+ ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
+ assertEquals(10, strings.size());
+ assertEquals(expectId, strings.get(0).getTenantId().getTenantId().intValue());
+ assertEquals(expectDate, strings.get(0).getTenantId().getTenantDate());
});
}
+ @Interceptor
+ public static class MyInterceptor {
+
+ private final List myTenantIds;
+
+ public MyInterceptor(TenantId theTenantId) {
+ Validate.notNull(theTenantId);
+ myTenantIds = Collections.singletonList(theTenantId);
+ }
+
+ @Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE)
+ public TenantId tenantIdentifyCreate() {
+ TenantId retVal = myTenantIds.get(0);
+ ourLog.info("Returning tenant ID: {}", retVal);
+ return retVal;
+ }
+
+ }
+
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
index df24e65b0b4..2f2d9b7024d 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
@@ -20,10 +20,24 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
+import javax.persistence.Embedded;
+import javax.persistence.MappedSuperclass;
import java.io.Serializable;
+@MappedSuperclass
public abstract class BaseResourceIndex implements Serializable {
+ @Embedded
+ private TenantId myTenantId;
+
+ public TenantId getTenantId() {
+ return myTenantId;
+ }
+
+ public void setTenantId(TenantId theTenantId) {
+ myTenantId = theTenantId;
+ }
+
public abstract Long getId();
public abstract void setId(Long theId);
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
index b88d63e6cb1..032db5adac8 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
@@ -80,17 +80,6 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
@Temporal(TemporalType.TIMESTAMP)
private Date myUpdated;
- @Embedded
- private TenantId myTenantId;
-
- public TenantId getTenantId() {
- return myTenantId;
- }
-
- public void setTenantId(TenantId theTenantId) {
- myTenantId = theTenantId;
- }
-
/**
* Subclasses may override
*/
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
index cf6511f28ab..1fea6e588af 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
@@ -221,6 +221,13 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
@Transient
private transient ResourceHistoryTable myCurrentVersionEntity;
+ /**
+ * Constructor
+ */
+ public ResourceTable() {
+ super();
+ }
+
@Override
public ResourceTag addTag(TagDefinition theTag) {
for (ResourceTag next : getTags()) {
@@ -423,6 +430,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
return this;
}
+ @Override
public Collection getTags() {
if (myTags == null) {
myTags = new HashSet<>();
@@ -551,6 +559,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
retVal.setFhirVersion(getFhirVersion());
retVal.setDeleted(getDeleted());
retVal.setForcedId(getForcedId());
+ retVal.setTenantId(getTenantId());
retVal.getTags().clear();
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java
index 3143e8559cf..555f98cd016 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java
@@ -1,5 +1,8 @@
package ca.uhn.fhir.jpa.model.entity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.time.LocalDate;
@@ -12,7 +15,22 @@ public class TenantId implements Cloneable {
@Column(name = "TENANT_DATE", nullable = true)
private LocalDate myTenantDate;
- public Integer getTenantId() {
+ /**
+ * Constructor
+ */
+ public TenantId() {
+ super();
+ }
+
+ /**
+ * Constructor
+ */
+ public TenantId(int theTenantId, LocalDate theTenantDate) {
+ setTenantId(theTenantId);
+ setTenantDate(theTenantDate);
+ }
+
+ public Integer getTenantId() {
return myTenantId;
}
@@ -36,4 +54,12 @@ public class TenantId implements Cloneable {
.setTenantId(getTenantId())
.setTenantDate(getTenantDate());
}
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("id", myTenantId)
+ .append("date", myTenantDate)
+ .toString();
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
index d5a9a547828..98265649049 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
@@ -24,9 +24,6 @@ import ca.uhn.fhir.rest.api.Constants;
public class JpaConstants {
- public static final String USERDATA_TENANT_ID = JpaConstants.class.getName() + "_USERDATA_TENANT_ID";
- public static final String USERDATA_TENANT_DATE = JpaConstants.class.getName() + "_USERDATA_TENANT_DATE";
-
/**
* Operation name for the $apply-codesystem-delta-add operation
*/
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java
index fdb1dccca7d..e66b4eb4e26 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java
@@ -9,6 +9,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.PostgreSQL94Dialect;
import org.hl7.fhir.dstu2.model.Subscription;
import org.springframework.beans.factory.annotation.Value;
@@ -94,6 +95,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
retVal.setUsername(myDbUsername);
retVal.setPassword(myDbPassword);
retVal.setDefaultQueryTimeout(20);
+ retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
return retVal;
}
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java
index eed61ff64af..e174228ffe1 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java
@@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.PostgreSQL94Dialect;
import org.hl7.fhir.dstu2.model.Subscription;
import org.springframework.beans.factory.annotation.Autowire;
@@ -101,6 +102,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
retVal.setUsername(myDbUsername);
retVal.setPassword(myDbPassword);
retVal.setDefaultQueryTimeout(20);
+ retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
return retVal;
}
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java
index 6b836aab1b4..d290cf3c385 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java
@@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.PostgreSQL94Dialect;
import org.hl7.fhir.dstu2.model.Subscription;
import org.springframework.beans.factory.annotation.Autowire;
@@ -86,6 +87,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
retVal.setUsername(myDbUsername);
retVal.setPassword(myDbPassword);
retVal.setDefaultQueryTimeout(20);
+ retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
return retVal;
}
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java
index c71bfd1259c..922e2087dad 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java
@@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.PostgreSQL94Dialect;
import org.hl7.fhir.dstu2.model.Subscription;
import org.springframework.beans.factory.annotation.Autowire;
@@ -86,6 +87,7 @@ public class TestR5Config extends BaseJavaConfigR5 {
retVal.setUsername(myDbUsername);
retVal.setPassword(myDbPassword);
retVal.setDefaultQueryTimeout(20);
+ retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
return retVal;
}
diff --git a/pom.xml b/pom.xml
index 2ef99fd2479..719bb34d765 100644
--- a/pom.xml
+++ b/pom.xml
@@ -645,7 +645,7 @@
3.0.2
6.1.0
- 5.4.10.Final
+ 5.4.11.Final
5.11.3.Final
5.5.5
@@ -1326,7 +1326,7 @@
org.postgresql
postgresql
- 42.2.9
+ 42.2.10
org.quartz-scheduler
From 1d1aadb81300c2b84296bab1ca410185d9673c1f Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Sun, 9 Feb 2020 20:57:50 -0500
Subject: [PATCH 03/63] Work on multitenancy
---
...sourceIndexedCompositeStringUniqueDao.java | 8 +--
.../IResourceIndexedSearchParamStringDao.java | 10 +--
.../fhir/jpa/dao/data/IResourceLinkDao.java | 14 ++--
.../jpa/dao/data/ISearchParamPresentDao.java | 20 +++---
.../jpa/sp/SearchParamPresenceSvcImpl.java | 1 +
.../fhir/jpa/dao/r4/MultitenantR4Test.java | 72 ++++++++++++++++++-
.../ResourceIndexedCompositeStringUnique.java | 11 +++
.../jpa/model/entity/SearchParamPresent.java | 13 +++-
.../uhn/fhir/jpa/model/entity/TenantId.java | 16 ++---
9 files changed, 127 insertions(+), 38 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedCompositeStringUniqueDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedCompositeStringUniqueDao.java
index c1c2bf8d069..29ea1e54730 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedCompositeStringUniqueDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedCompositeStringUniqueDao.java
@@ -25,15 +25,13 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
-import java.util.Collection;
-import java.util.Optional;
+import java.util.List;
public interface IResourceIndexedCompositeStringUniqueDao extends JpaRepository {
@Query("SELECT r FROM ResourceIndexedCompositeStringUnique r WHERE r.myIndexString = :str")
ResourceIndexedCompositeStringUnique findByQueryString(@Param("str") String theQueryString);
- @Query("SELECT r.myResourceId FROM ResourceIndexedCompositeStringUnique r WHERE r.myIndexString IN :str")
- Collection findResourcePidsByQueryStrings(@Param("str") Collection theQueryString);
-
+ @Query("SELECT r FROM ResourceIndexedCompositeStringUnique r WHERE r.myResourceId = :resId")
+ List findAllForResourceId(@Param("resId") Long theResourceId);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java
index 32b1b10deca..00910347186 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java
@@ -20,9 +20,8 @@ package ca.uhn.fhir.jpa.dao.data;
* #L%
*/
-import org.springframework.data.jpa.repository.JpaRepository;
-
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
+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;
@@ -32,6 +31,9 @@ import java.util.List;
public interface IResourceIndexedSearchParamStringDao extends JpaRepository {
@Modifying
- @Query("delete from ResourceIndexedSearchParamString t WHERE t.myResourcePid = :resid")
- void deleteByResourceId(@Param("resid") Long theResourcePid);
+ @Query("DELETE FROM ResourceIndexedSearchParamString t WHERE t.myResourcePid = :resId")
+ void deleteByResourceId(@Param("resId") Long theResourcePid);
+
+ @Query("SELECT t FROM ResourceIndexedSearchParamString 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/data/IResourceLinkDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceLinkDao.java
index 51b9059a34d..d9cf4628516 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceLinkDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceLinkDao.java
@@ -20,16 +20,20 @@ package ca.uhn.fhir.jpa.dao.data;
* #L%
*/
-import org.springframework.data.jpa.repository.JpaRepository;
-
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
+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;
-public interface IResourceLinkDao extends JpaRepository {
+import java.util.List;
+
+public interface IResourceLinkDao extends JpaRepository {
@Modifying
- @Query("delete from ResourceLink t WHERE t.mySourceResourcePid = :resid")
- void deleteByResourceId(@Param("resid") Long theResourcePid);
+ @Query("DELETE FROM ResourceLink t WHERE t.mySourceResourcePid = :resId")
+ void deleteByResourceId(@Param("resId") Long theResourcePid);
+
+ @Query("SELECT t FROM ResourceLink t WHERE t.mySourceResourcePid = :resId")
+ List findAllForResourceId(@Param("resId") Long thePatientId);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java
index d965b5e2f88..30b9ec80fb3 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java
@@ -1,7 +1,13 @@
package ca.uhn.fhir.jpa.dao.data;
-import java.util.Collection;
-import java.util.Date;
+import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
+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;
/*
* #%L
@@ -23,18 +29,10 @@ import java.util.Date;
* #L%
*/
-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 ca.uhn.fhir.jpa.model.entity.ResourceTable;
-import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
-
public interface ISearchParamPresentDao extends JpaRepository {
@Query("SELECT s FROM SearchParamPresent s WHERE s.myResource = :res")
- Collection findAllForResource(@Param("res") ResourceTable theResource);
+ List findAllForResource(@Param("res") ResourceTable theResource);
@Modifying
@Query("delete from SearchParamPresent t WHERE t.myResourcePid = :resid")
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java
index 925808c3b34..d37f27819fb 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java
@@ -66,6 +66,7 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
present.setResource(theResource);
present.setParamName(paramName);
present.setPresent(next.getValue());
+ present.setTenantId(theResource.getTenantId());
present.calculateHashes();
newHashToPresence.put(present.getHashPresence(), present);
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 23597b1e688..33b975a8947 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,13 +4,22 @@ 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.ResourceHistoryTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
+import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.model.entity.TenantId;
-import ca.uhn.fhir.jpa.model.util.JpaConstants;
+import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.util.TestUtil;
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.Organization;
import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@@ -19,6 +28,7 @@ import org.junit.Test;
import javax.servlet.ServletException;
import java.time.LocalDate;
import java.time.Month;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -62,30 +72,88 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
@Test
public void testCreateResourceWithTenant() {
+ createUniqueCompositeSp();
+
int expectId = 3;
LocalDate expectDate = LocalDate.of(2020, Month.JANUARY, 14);
myInterceptorRegistry.registerInterceptor(new MyInterceptor(new TenantId(expectId, expectDate)));
+ Organization org = new Organization();
+ org.setName("org");
+ IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
+
Patient p = new Patient();
p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
+ p.getManagingOrganization().setReferenceElement(orgId);
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
runInTransaction(() -> {
+ // HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(expectId, resourceTable.getTenantId().getTenantId().intValue());
assertEquals(expectDate, resourceTable.getTenantId().getTenantDate());
- List strings = myResourceIndexedSearchParamStringDao.findAll();
+ // HFJ_RES_VER
+ ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
+ assertEquals(expectId, version.getTenantId().getTenantId().intValue());
+ assertEquals(expectDate, version.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(expectId, strings.get(0).getTenantId().getTenantId().intValue());
assertEquals(expectDate, strings.get(0).getTenantId().getTenantDate());
+
+ // HFJ_RES_LINK
+ List resourceLinks = myResourceLinkDao.findAllForResourceId(patientId);
+ assertEquals(1, resourceLinks.size());
+ assertEquals(expectId, resourceLinks.get(0).getTenantId().getTenantId().intValue());
+ assertEquals(expectDate, resourceLinks.get(0).getTenantId().getTenantDate());
+
+ // HFJ_RES_PARAM_PRESENT
+ List presents = mySearchParamPresentDao.findAllForResource(resourceTable);
+ assertEquals(3, presents.size());
+ assertEquals(expectId, presents.get(0).getTenantId().getTenantId().intValue());
+ assertEquals(expectDate, presents.get(0).getTenantId().getTenantDate());
+
+ // HFJ_IDX_CMP_STRING_UNIQ
+ List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId);
+ assertEquals(3, uniques.size());
+ assertEquals(expectId, uniques.get(0).getTenantId().getTenantId().intValue());
+ assertEquals(expectDate, uniques.get(0).getTenantId().getTenantDate());
});
}
+ private void createUniqueCompositeSp() {
+ 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");
+ mySearchParameterDao.update(sp);
+
+ sp = new SearchParameter();
+ sp.setId("SearchParameter/patient-birthdate");
+ sp.setType(Enumerations.SearchParamType.COMPOSITE);
+ sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
+ sp.addBase("Patient");
+ sp.addComponent()
+ .setExpression("Patient")
+ .setDefinition("SearchParameter/patient-birthdate");
+ sp.addExtension()
+ .setUrl(SearchParamConstants.EXT_SP_UNIQUE)
+ .setValue(new BooleanType(true));
+ mySearchParameterDao.update(sp);
+
+ mySearchParamRegistry.forceRefresh();
+ }
+
@Interceptor
public static class MyInterceptor {
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 2c6305ebdd8..27870d6e608 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
@@ -48,6 +48,8 @@ public class ResourceIndexedCompositeStringUnique implements Comparable
Date: Wed, 4 Mar 2020 13:49:36 -0500
Subject: [PATCH 04/63] Add columns
---
.../fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java | 3 +++
1 file changed, 3 insertions(+)
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 15dde30bba0..200585874e6 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
@@ -47,6 +47,9 @@ public class ResourceHistoryProvenanceEntity {
private String mySourceUri;
@Column(name = "REQUEST_ID", length = Constants.REQUEST_ID_LENGTH, nullable = true)
private String myRequestId;
+ // FIXME: make sure this gets populated
+ @Embedded
+ private TenantId myTenantId;
public ResourceTable getResourceTable() {
return myResourceTable;
From 9df4c5812279ad58502eb3f4b5d695f171cab6d0 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Fri, 27 Mar 2020 09:22:00 -0400
Subject: [PATCH 05/63] Work on multitenancy
---
.../hapi/fhir/changelog/4_3_0/changes.yaml | 5 --
.../fhir/jpa/dao/r4/MultitenantR4Test.java | 80 ++++++++++++++-----
.../tasks/HapiFhirJpaMigrationTasks.java | 7 +-
.../fhir/jpa/model/entity/ResourceTable.java | 16 ----
.../fhir/jpa/model/entity/ResourceTag.java | 1 +
5 files changed, 66 insertions(+), 43 deletions(-)
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
index d1ed402450b..08691d1826a 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
@@ -25,8 +25,3 @@
has been replaced with an equivalent `FhirContext.newFhirPath()`. The FhirPath expression language was initially
called FluentPath before being renamed, so this change brings HAPI FHIR inline with the correct naming.
"
-- item:
- type: change
- title: "The JPA server now shared a single set of tags for all versions of a resource, bringing it in line with the
- functional description in the FHIR specification. This means that it is no longer possible to modify the tags for
- a specific version of a resource, and also causes significant performance improvements in some cases."
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 33b975a8947..f62bc29557e 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
@@ -28,6 +28,7 @@ import org.junit.Test;
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;
@@ -36,16 +37,19 @@ import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
public class MultitenantR4Test extends BaseJpaR4SystemTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MultitenantR4Test.class);
+ private MyInterceptor myTenantInterceptor;
@After
public void after() {
myDaoConfig.setMultiTenancyEnabled(new DaoConfig().isMultiTenancyEnabled());
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
+ myInterceptor = null;
}
@Override
@@ -74,56 +78,84 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
public void testCreateResourceWithTenant() {
createUniqueCompositeSp();
- int expectId = 3;
- LocalDate expectDate = LocalDate.of(2020, Month.JANUARY, 14);
- myInterceptorRegistry.registerInterceptor(new MyInterceptor(new TenantId(expectId, expectDate)));
+ addTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
+ addTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
Organization org = new Organization();
org.setName("org");
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
+ p.getMeta().addTag("http://system", "code", "diisplay");
p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
p.getManagingOrganization().setReferenceElement(orgId);
- Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
+ when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
+ Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
- assertEquals(expectId, resourceTable.getTenantId().getTenantId().intValue());
- assertEquals(expectDate, resourceTable.getTenantId().getTenantDate());
+ assertEquals(3, resourceTable.getTenantId().getTenantId().intValue());
+ assertEquals(LocalDate.of(2020, Month.JANUARY, 14), resourceTable.getTenantId().getTenantDate());
+
+ resourceTable.getProfile()
// HFJ_RES_VER
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
- assertEquals(expectId, version.getTenantId().getTenantId().intValue());
- assertEquals(expectDate, version.getTenantId().getTenantDate());
+ assertEquals(3, version.getTenantId().getTenantId().intValue());
+ assertEquals(LocalDate.of(2020, Month.JANUARY, 14), version.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(expectId, strings.get(0).getTenantId().getTenantId().intValue());
- assertEquals(expectDate, strings.get(0).getTenantId().getTenantDate());
+ assertEquals(3, strings.get(0).getTenantId().getTenantId().intValue());
+ assertEquals(LocalDate.of(2020, Month.JANUARY, 14), strings.get(0).getTenantId().getTenantDate());
// HFJ_RES_LINK
List resourceLinks = myResourceLinkDao.findAllForResourceId(patientId);
assertEquals(1, resourceLinks.size());
- assertEquals(expectId, resourceLinks.get(0).getTenantId().getTenantId().intValue());
- assertEquals(expectDate, resourceLinks.get(0).getTenantId().getTenantDate());
+ assertEquals(3, resourceLinks.get(0).getTenantId().getTenantId().intValue());
+ assertEquals(LocalDate.of(2020, Month.JANUARY, 14), resourceLinks.get(0).getTenantId().getTenantDate());
// HFJ_RES_PARAM_PRESENT
List presents = mySearchParamPresentDao.findAllForResource(resourceTable);
assertEquals(3, presents.size());
- assertEquals(expectId, presents.get(0).getTenantId().getTenantId().intValue());
- assertEquals(expectDate, presents.get(0).getTenantId().getTenantDate());
+ assertEquals(3, presents.get(0).getTenantId().getTenantId().intValue());
+ assertEquals(LocalDate.of(2020, Month.JANUARY, 14), presents.get(0).getTenantId().getTenantDate());
// HFJ_IDX_CMP_STRING_UNIQ
List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId);
assertEquals(3, uniques.size());
- assertEquals(expectId, uniques.get(0).getTenantId().getTenantId().intValue());
- assertEquals(expectDate, uniques.get(0).getTenantId().getTenantDate());
+ assertEquals(3, uniques.get(0).getTenantId().getTenantId().intValue());
+ assertEquals(LocalDate.of(2020, Month.JANUARY, 14), uniques.get(0).getTenantId().getTenantDate());
+ });
+
+ }
+
+ @Test
+ public void testUpdateResourceWithTenant() {
+ createUniqueCompositeSp();
+
+ addTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
+
+ Patient p = new Patient();
+ p.setActive(true);
+ Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
+
+ p = new Patient();
+ p.setId("Patient/" + patientId);
+ p.setActive(false);
+ myPatientDao.update(p);
+
+ runInTransaction(() -> {
+ // HFJ_RES_VER
+ ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 2);
+ assertEquals(tenantId, resVer.getTenantId().getTenantId().intValue());
+ assertEquals(tenantDate, resVer.getTenantId().getTenantDate());
+
});
}
@@ -154,19 +186,27 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
mySearchParamRegistry.forceRefresh();
}
+ public void addTenant(int theTenantId, LocalDate theTenantDate) {
+ if (myTenantInterceptor == null) {
+ myTenantInterceptor = new MyInterceptor();
+ myInterceptorRegistry.registerInterceptor(myInterceptor);
+ }
+ myTenantInterceptor.addTenant(new TenantId(theTenantId, theTenantDate));
+ }
+
@Interceptor
public static class MyInterceptor {
- private final List myTenantIds;
+ private final List myTenantIds = new ArrayList<>();
- public MyInterceptor(TenantId theTenantId) {
+ public void addTenant(TenantId theTenantId) {
Validate.notNull(theTenantId);
- myTenantIds = Collections.singletonList(theTenantId);
+ myTenantIds.add(theTenantId);
}
@Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE)
public TenantId tenantIdentifyCreate() {
- TenantId retVal = myTenantIds.get(0);
+ TenantId retVal = myTenantIds.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 31a71c7b1f1..057c28aaafd 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
@@ -57,10 +57,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
init400(); // 20190401 - 20190814
init410(); // 20190815 - 20191014
init420(); // 20191015 - 20200217
- init430(); // 20200218 - present
+ init500(); // 20200218 - present
}
- protected void init430() { // 20200218 - present
+ protected void init500() { // 20200218 - present
Builder version = forVersion(VersionEnum.V4_3_0);
// Eliminate circular dependency.
@@ -68,6 +68,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
version.onTable("HFJ_RES_VER").dropColumn("20200218.2", "FORCED_ID_PID");
version.onTable("HFJ_RES_VER").addForeignKey("20200218.3", "FK_RESOURCE_HISTORY_RESOURCE").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
version.onTable("HFJ_RES_VER").modifyColumn("20200220.1", "RES_ID").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
+
+ // Add mlutiitenancy
+ version.onTable("HFJ_RESOURCE").dropColumn("20200327.1", "RES_PROFILE");
}
protected void init420() { // 20191015 - 20200217
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
index 731227e8af4..0f1ffb8f769 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
@@ -60,7 +60,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource, IResourceLookup {
public static final int RESTYPE_LEN = 40;
private static final int MAX_LANGUAGE_LENGTH = 20;
- private static final int MAX_PROFILE_LENGTH = 200;
private static final long serialVersionUID = 1L;
/**
@@ -167,10 +166,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
@OptimisticLock(excluded = true)
private boolean myParamsUriPopulated;
- @Column(name = "RES_PROFILE", length = MAX_PROFILE_LENGTH, nullable = true)
- @OptimisticLock(excluded = true)
- private String myProfile;
-
// Added in 3.0.0 - Should make this a primitive Boolean at some point
@OptimisticLock(excluded = true)
@Column(name = "SP_CMPSTR_UNIQ_PRESENT")
@@ -402,17 +397,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
getParamsUri().addAll(theParamsUri);
}
- public String getProfile() {
- return myProfile;
- }
-
- public void setProfile(String theProfile) {
- if (defaultString(theProfile).length() > MAX_PROFILE_LENGTH) {
- throw new UnprocessableEntityException("Profile name exceeds maximum length of " + MAX_PROFILE_LENGTH + " chars: " + theProfile);
- }
- myProfile = theProfile;
- }
-
@Override
public Long getResourceId() {
return getId();
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java
index 7d06439837c..a7e2cd1c950 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java
@@ -50,6 +50,7 @@ public class ResourceTag extends BaseTag {
@Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourceId;
+
@Embedded
private TenantId myTenantId;
From 691f2c4e9a681780cf8c07af3d7fe003f3d2e8cf Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Sun, 29 Mar 2020 13:35:20 -0400
Subject: [PATCH 06/63] Work on muiltitenancy
---
hapi-fhir-converter/pom.xml | 6 +
...83-fail-if-overriding-sps-is-disabled.yaml | 7 +
.../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 2 +
.../FhirResourceDaoSearchParameterDstu2.java | 7 +-
.../IResourceIndexedSearchParamDateDao.java | 8 +-
.../FhirResourceDaoSearchParameterDstu3.java | 3 +-
.../r4/FhirResourceDaoSearchParameterR4.java | 21 +-
.../r5/FhirResourceDaoSearchParameterR5.java | 3 +-
.../dao/r4/FhirResourceDaoR4CreateTest.java | 45 ++++-
...hirResourceDaoR4UniqueSearchParamTest.java | 21 +-
.../fhir/jpa/dao/r4/MultitenantR4Test.java | 185 ++++++++++++++----
.../tasks/HapiFhirJpaMigrationTasks.java | 3 +-
.../uhn/fhir/jpa/model/entity/ForcedId.java | 10 +
.../ResourceHistoryProvenanceEntity.java | 19 +-
.../ResourceIndexedCompositeStringUnique.java | 1 +
.../ResourceIndexedSearchParamDate.java | 3 +-
.../fhir/jpa/model/entity/ResourceTable.java | 1 -
17 files changed, 275 insertions(+), 70 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-fail-if-overriding-sps-is-disabled.yaml
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
Date: Sun, 29 Mar 2020 18:16:04 -0400
Subject: [PATCH 07/63] Work on multitenancy
---
.../ca/uhn/fhir/interceptor/api/Pointcut.java | 70 ++-
.../ca/uhn/fhir/i18n/hapi-messages.properties | 3 +
.../ca/uhn/hapi/fhir/atlas/points.json | 2 +
.../hapi/fhir/changelog/4_3_0/changes.yaml | 1 +
.../ca/uhn/hapi/fhir/docs/files.properties | 1 +
.../hapi/fhir/docs/server_jpa/partitioning.md | 18 +
.../fhir/jpa/bulk/BulkDataExportSvcImpl.java | 2 +-
.../ca/uhn/fhir/jpa/config/BaseConfig.java | 6 +
.../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 4 +-
.../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 131 +++--
.../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 14 +-
.../dao/FhirResourceDaoSubscriptionDstu2.java | 2 +-
.../fhir/jpa/dao/FulltextSearchSvcImpl.java | 10 +-
.../ca/uhn/fhir/jpa/dao/ISearchBuilder.java | 7 +-
.../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 22 +-
.../uhn/fhir/jpa/dao/data/IForcedIdDao.java | 3 +
.../FhirResourceDaoSubscriptionDstu3.java | 2 +-
.../dao/index/DaoSearchParamSynchronizer.java | 2 +-
.../fhir/jpa/dao/index/IdHelperService.java | 31 +-
.../RequestPartitionHelperService.java | 101 ++++
.../dao/predicate/BasePredicateBuilder.java | 7 +-
.../jpa/dao/predicate/IPredicateBuilder.java | 4 +-
.../jpa/dao/predicate/PredicateBuilder.java | 16 +-
.../predicate/PredicateBuilderReference.java | 65 ++-
.../predicate/PredicateBuilderResourceId.java | 10 +-
.../dao/predicate/PredicateBuilderUri.java | 11 +-
.../dao/r4/FhirResourceDaoSubscriptionR4.java | 2 +-
.../dao/r5/FhirResourceDaoSubscriptionR5.java | 2 +-
.../jpa/search/ISearchCoordinatorSvc.java | 2 +-
.../jpa/search/SearchCoordinatorSvcImpl.java | 42 +-
.../jpa/sp/SearchParamPresenceSvcImpl.java | 2 +-
.../term/TermCodeSystemStorageSvcImpl.java | 6 +-
.../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 2 +
.../fhir/jpa/dao/r4/MultitenantR4Test.java | 334 ------------
.../fhir/jpa/dao/r4/PartitioningR4Test.java | 499 ++++++++++++++++++
.../search/SearchCoordinatorSvcImplTest.java | 17 +-
.../jpa/model/entity/BaseHasResource.java | 17 +-
.../jpa/model/entity/BaseResourceIndex.java | 18 +-
.../ca/uhn/fhir/jpa/model/entity/BaseTag.java | 12 +
.../uhn/fhir/jpa/model/entity/ForcedId.java | 11 +-
.../fhir/jpa/model/entity/PartitionId.java | 66 +++
.../ResourceHistoryProvenanceEntity.java | 10 +-
.../model/entity/ResourceHistoryTable.java | 4 +-
.../jpa/model/entity/ResourceHistoryTag.java | 3 +-
.../ResourceIndexedCompositeStringUnique.java | 14 +-
.../fhir/jpa/model/entity/ResourceTable.java | 4 +-
.../fhir/jpa/model/entity/ResourceTag.java | 10 +-
.../jpa/model/entity/SearchParamPresent.java | 12 +-
.../uhn/fhir/jpa/model/entity/TenantId.java | 61 ---
pom.xml | 2 +-
50 files changed, 1085 insertions(+), 612 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md
create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/partition/RequestPartitionHelperService.java
delete mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/MultitenantR4Test.java
create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionId.java
delete mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TenantId.java
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
index e569f7ca872..8a5d1a6f034 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
@@ -27,7 +27,11 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import javax.annotation.Nonnull;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/**
* Value for {@link Hook#value()}
@@ -374,7 +378,6 @@ public enum Pointcut {
),
-
/**
* Server Hook:
* This method is called after the server implementation method has been called, but before any attempt
@@ -1323,14 +1326,16 @@ public enum Pointcut {
/**
* Storage Hook:
- * Invoked before an $expunge
operation on all data (expungeEverything) is called.
+ * Invoked before FHIR create operation to request the identification of the partition ID to be associated
+ * with the resource being created. This hook will only be called if partitioning is enabled in the JPA
+ * server.
*
- * Hooks will be passed a reference to a counter containing the current number of records that have been deleted.
- * If the hook deletes any records, the hook is expected to increment this counter by the number of records deleted.
- *
* Hooks may accept the following parameters:
+ *
*
+ *
* org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned.
+ *
*
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
@@ -1346,18 +1351,53 @@ public enum Pointcut {
*
*
*
- * Hooks should return an instance of ca.uhn.fhir.jpa.model.entity.TenantId
or null
.
+ * Hooks should return an instance of ca.uhn.fhir.jpa.model.entity.PartitionId
or null
.
*
*/
- STORAGE_TENANT_IDENTIFY_CREATE (
+ STORAGE_PARTITION_IDENTIFY_CREATE(
// Return type
- "ca.uhn.fhir.jpa.model.entity.TenantId",
+ "ca.uhn.fhir.jpa.model.entity.PartitionId",
// Params
"org.hl7.fhir.instance.model.api.IBaseResource",
"ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
),
+ /**
+ * Storage Hook:
+ * Invoked before FHIR read/access operation (e.g. read/vread , search , history , etc.) operation to request the
+ * identification of the partition ID to be associated with the resource being created. This hook will only be called if
+ * partitioning is enabled in the JPA server.
+ *
+ * Hooks may accept the following parameters:
+ *
+ *
+ *
+ * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
+ * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
+ * pulled out of the servlet request. Note that the bean
+ * properties are not all guaranteed to be populated, depending on how early during processing the
+ * exception occurred.
+ *
+ *
+ * ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
+ * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
+ * pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
+ * only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
+ *
+ *
+ *
+ * Hooks should return an instance of ca.uhn.fhir.jpa.model.entity.PartitionId
or null
.
+ *
+ */
+ STORAGE_PARTITION_IDENTIFY_READ(
+ // Return type
+ "ca.uhn.fhir.jpa.model.entity.PartitionId",
+ // Params
+ "ca.uhn.fhir.rest.api.server.RequestDetails",
+ "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
+ ),
+
/**
* Performance Tracing Hook:
* This hook is invoked when any informational messages generated by the
@@ -1680,12 +1720,12 @@ public enum Pointcut {
*
*
* THIS IS AN EXPERIMENTAL HOOK AND MAY BE REMOVED OR CHANGED WITHOUT WARNING.
- *
- *
+ *
+ *
* Note that this is a performance tracing hook. Use with caution in production
* systems, since calling it may (or may not) carry a cost.
- *
- *
+ *
+ *
* Hooks may accept the following parameters:
*
*
@@ -1759,9 +1799,7 @@ public enum Pointcut {
* This pointcut is used only for unit tests. Do not use in production code as it may be changed or
* removed at any time.
*/
- TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName())
-
- ;
+ TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName());
private final List myParameterTypes;
private final Class> myReturnType;
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
index bc15d09dc64..a9a7992c8cb 100644
--- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
+++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
@@ -136,5 +136,8 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
+ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned
+
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"
+
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/atlas/points.json b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/atlas/points.json
index ad2a8e17898..248f9ec787c 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/atlas/points.json
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/atlas/points.json
@@ -20,6 +20,7 @@
"contactName": "Kevin Mayfield",
"contactEmail": "Kevin.mayfield@mayfield-is.co.uk",
"link": "https://data.developer.nhs.uk/ccri/exp",
+ "city": "UK",
"lat": 55.378052,
"lon": -3.435973,
"added": "2019-08-19"
@@ -29,6 +30,7 @@
"contactName": "David Hay",
"contactEmail": "david.hay25@gmail.com",
"link": "http://clinfhir.com",
+ "city": "New Zealand",
"lat": -42.651737,
"lon": 171.926909,
"added": "2019-08-18"
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
index 08691d1826a..d7523c9e168 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
@@ -6,6 +6,7 @@
Hibernate ORM (JPA): 5.4.6 -> 5.4.12
Hibernate Search (JPA): 5.11.3 -> 5.11.5
+ Guava (JPA): 28.0 -> 28.2
"
- item:
issue: "1583"
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
index a4f099499ce..0b651cb5814 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
@@ -44,6 +44,7 @@ page.server_jpa.architecture=Architecture
page.server_jpa.configuration=Configuration
page.server_jpa.search=Search
page.server_jpa.performance=Performance
+page.server_jpa.partitioning=Partitioning
page.server_jpa.upgrading=Upgrade Guide
section.interceptors.title=Interceptors
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md
new file mode 100644
index 00000000000..25f1534d8fb
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md
@@ -0,0 +1,18 @@
+# Partitioning
+
+# Limitations
+
+Partitioning is a relatively new feature in HAPI FHIR and has a number of known limitations. If you are intending to use partitioning for achieving a multi-tenant architecture it is important to carefully consider these limitations.
+
+None of the limitations listed here are considered permanent. Over time the HAPI FHIR team are hoping to make all of these features partition aware.
+
+* **Subscriptions may not be partitioned**: All subscriptions must be placed in the default partition, and subscribers will receive deliveries for any matching resources from all partitions.
+
+* **Conformance resources may not be partitioned**: The following resources must be placed in the default partition, and will be shared for any validation activities across all partitions:
+ * StructureDefinition
+ * Questionnaire
+ * ValueSet
+ * CodeSystem
+ * ConceptMap
+
+* **Bulk Operations are not partition aware**: Bulk export operations will export data across all partitions.
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java
index 072565cdc43..528ff4d476c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java
@@ -221,7 +221,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
map.setLastUpdated(new DateRangeParam(job.getSince(), null));
}
- IResultIterator resultIterator = sb.createQuery(map, new SearchRuntimeDetails(null, theJobUuid), null);
+ IResultIterator resultIterator = sb.createQuery(map, new SearchRuntimeDetails(null, theJobUuid), null, null);
storeResultsToFiles(nextCollection, sb, resultIterator, jobResourceCounter, jobStopwatch);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
index 11163796b33..9a2b078b889 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
@@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
@@ -231,6 +232,11 @@ public abstract class BaseConfig {
return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer());
}
+ @Bean
+ public RequestPartitionHelperService requestPartitionHelperService() {
+ return new RequestPartitionHelperService();
+ }
+
@Bean
public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
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 20d30f7b211..0231f9f4dd7 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,7 +219,7 @@ public abstract class BaseHapiFhirDao extends BaseStora
retVal.setResourceType(theEntity.getResourceType());
retVal.setForcedId(theId.getIdPart());
retVal.setResource(theEntity);
- retVal.setTenantId(theEntity.getTenantId());
+ retVal.setPartitionId(theEntity.getPartitionId());
theEntity.setForcedId(retVal);
}
}
@@ -1095,7 +1095,7 @@ public abstract class BaseHapiFhirDao extends BaseStora
ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity();
provenance.setResourceHistoryTable(historyEntry);
provenance.setResourceTable(entity);
- provenance.setTenantId(entity.getTenantId());
+ provenance.setPartitionId(entity.getPartitionId());
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/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
index 01ba1fa05f5..28bff67d924 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
@@ -24,10 +24,18 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.delete.DeleteConflictList;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
-import ca.uhn.fhir.jpa.model.entity.*;
+import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
+import ca.uhn.fhir.jpa.model.entity.BaseTag;
+import ca.uhn.fhir.jpa.model.entity.ForcedId;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
+import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.model.entity.TagDefinition;
+import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
@@ -40,18 +48,46 @@ import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.rest.api.*;
-import ca.uhn.fhir.rest.api.server.*;
-import ca.uhn.fhir.rest.server.exceptions.*;
+import ca.uhn.fhir.rest.api.CacheControlDirective;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.EncodingEnum;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PatchTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.api.ValidationModeEnum;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails;
+import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
+import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
+import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
+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.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.StopWatch;
-import ca.uhn.fhir.validation.*;
+import ca.uhn.fhir.validation.FhirValidator;
+import ca.uhn.fhir.validation.IInstanceValidatorModule;
+import ca.uhn.fhir.validation.IValidationContext;
+import ca.uhn.fhir.validation.IValidatorModule;
+import ca.uhn.fhir.validation.ValidationOptions;
+import ca.uhn.fhir.validation.ValidationResult;
import org.apache.commons.lang3.Validate;
-import org.hl7.fhir.instance.model.api.*;
+import org.hl7.fhir.instance.model.api.IBaseCoding;
+import org.hl7.fhir.instance.model.api.IBaseMetaType;
+import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.PlatformTransactionManager;
@@ -67,7 +103,14 @@ import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@@ -94,6 +137,8 @@ public abstract class BaseHapiFhirResourceDao extends B
private IInstanceValidatorModule myInstanceValidator;
private String myResourceName;
private Class myResourceType;
+ @Autowired
+ private RequestPartitionHelperService myRequestPartitionHelperService;
@Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) {
@@ -161,7 +206,8 @@ public abstract class BaseHapiFhirResourceDao extends B
theResource.setId(UUID.randomUUID().toString());
}
- return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails);
+ PartitionId partitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource);
+ return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails, partitionId);
}
@Override
@@ -183,7 +229,7 @@ public abstract class BaseHapiFhirResourceDao extends B
validateIdPresentForDelete(theId);
validateDeleteEnabled();
- final ResourceTable entity = readEntityLatestVersion(theId);
+ final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
}
@@ -390,7 +436,7 @@ public abstract class BaseHapiFhirResourceDao extends B
}
}
- private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequest) {
+ private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequest, PartitionId thePartitionId) {
StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource);
@@ -398,17 +444,9 @@ public abstract class BaseHapiFhirResourceDao extends B
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
- if (myDaoConfig.isMultiTenancyEnabled()) {
- // Interceptor call: STORAGE_TENANT_IDENTIFY_CREATE
- HookParams params = new HookParams()
- .add(IBaseResource.class, theResource)
- .add(RequestDetails.class, theRequest)
- .addIfMatchesType(ServletRequestDetails.class, theRequest);
- TenantId tenantId = (TenantId) doCallHooksAndReturnObject(theRequest, Pointcut.STORAGE_TENANT_IDENTIFY_CREATE, params);
- if (tenantId != null) {
- ourLog.debug("Resource has been assigned tenant ID: {}", tenantId);
- entity.setTenantId(tenantId);
- }
+ if (thePartitionId != null) {
+ ourLog.debug("Resource has been assigned partition ID: {}", thePartitionId);
+ entity.setPartitionId(thePartitionId);
}
if (isNotBlank(theIfNoneExist)) {
@@ -709,7 +747,7 @@ public abstract class BaseHapiFhirResourceDao extends B
throw new ResourceNotFoundException(theResourceId);
}
- ResourceTable latestVersion = readEntityLatestVersion(theResourceId);
+ ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
if (latestVersion.getVersion() != entity.getVersion()) {
doMetaAdd(theMetaAdd, entity);
} else {
@@ -741,7 +779,7 @@ public abstract class BaseHapiFhirResourceDao extends B
throw new ResourceNotFoundException(theResourceId);
}
- ResourceTable latestVersion = readEntityLatestVersion(theResourceId);
+ ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
if (latestVersion.getVersion() != entity.getVersion()) {
doMetaDelete(theMetaDel, entity);
} else {
@@ -817,7 +855,7 @@ public abstract class BaseHapiFhirResourceDao extends B
}
} else {
- entityToUpdate = readEntityLatestVersion(theId);
+ entityToUpdate = readEntityLatestVersion(theId, theRequest);
if (theId.hasVersionIdPart()) {
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
@@ -944,7 +982,6 @@ public abstract class BaseHapiFhirResourceDao extends B
@Override
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
-
return readEntity(theId, true, theRequest);
}
@@ -952,9 +989,23 @@ public abstract class BaseHapiFhirResourceDao extends B
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
validateResourceTypeAndThrowInvalidRequestException(theId);
- ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(getResourceName(), theId.getIdPart());
+ @Nullable PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
+ ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(partitionId, getResourceName(), theId.getIdPart());
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong());
+ // Verify that the resource is for the correct partition
+ if (partitionId != null) {
+ if (entity.getPartitionId() != null) {
+ if (!entity.getPartitionId().getPartitionId().equals(partitionId.getPartitionId())) {
+ ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", partitionId, entity.getPartitionId());
+ entity = null;
+ }
+ } else {
+ ourLog.debug("Performing a read for PartitionId=null but entity has partition: {}", entity.getPartitionId());
+ entity = null;
+ }
+ }
+
if (entity == null) {
throw new ResourceNotFoundException(theId);
}
@@ -990,8 +1041,17 @@ public abstract class BaseHapiFhirResourceDao extends B
return entity;
}
- protected ResourceTable readEntityLatestVersion(IIdType theId) {
- ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(getResourceName(), theId.getIdPart());
+ @NotNull
+ protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails) {
+ PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, getResourceName());
+ return readEntityLatestVersion(theId, partitionId);
+ }
+
+ @NotNull
+ private ResourceTable readEntityLatestVersion(IIdType theId, @Nullable PartitionId thePartitionId) {
+ validateResourceTypeAndThrowInvalidRequestException(theId);
+
+ ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(thePartitionId, getResourceName(), theId.getIdPart());
ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId());
if (entity == null) {
throw new ResourceNotFoundException(theId);
@@ -1127,7 +1187,9 @@ public abstract class BaseHapiFhirResourceDao extends B
String uuid = UUID.randomUUID().toString();
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
- try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest)) {
+
+ PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
+ try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, partitionId)) {
while (iter.hasNext()) {
retVal.add(iter.next());
}
@@ -1231,10 +1293,11 @@ public abstract class BaseHapiFhirResourceDao extends B
*/
resourceId = theResource.getIdElement();
+ PartitionId partitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource);
try {
- entity = readEntityLatestVersion(resourceId);
+ entity = readEntityLatestVersion(resourceId, partitionId);
} catch (ResourceNotFoundException e) {
- return doCreate(theResource, null, thePerformIndexing, new Date(), theRequest);
+ return doCreate(theResource, null, thePerformIndexing, new Date(), theRequest, partitionId);
}
}
@@ -1282,10 +1345,6 @@ public abstract class BaseHapiFhirResourceDao extends B
ResourceTable savedEntity = updateInternal(theRequest, theResource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource);
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource).setCreated(wasDeleted);
- if (!thePerformIndexing) {
- outcome.setId(theResource.getIdElement());
- }
-
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart());
outcome.setOperationOutcome(createInfoOperationOutcome(msg));
@@ -1304,7 +1363,7 @@ public abstract class BaseHapiFhirResourceDao extends B
if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
}
- final ResourceTable entity = readEntityLatestVersion(theId);
+ final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
// Validate that there are no resources pointing to the candidate that
// would prevent deletion
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
index 2b36bfb3c58..b21a4c883ca 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
@@ -191,7 +191,7 @@ public class DaoConfig {
/**
* @since 5.0.0
*/
- private boolean myMultiTenancyEnabled;
+ private boolean myPartitioningEnabled;
/**
* Constructor
@@ -1944,21 +1944,21 @@ public class DaoConfig {
}
/**
- * If enabled (default is false
) the JPA server will support multitenant queries
+ * If enabled (default is false
) the JPA server will support data partitioning
*
* @since 5.0.0
*/
- public void setMultiTenancyEnabled(boolean theMultiTenancyEnabled) {
- myMultiTenancyEnabled = theMultiTenancyEnabled;
+ public void setPartitioningEnabled(boolean theMultiTenancyEnabled) {
+ myPartitioningEnabled = theMultiTenancyEnabled;
}
/**
- * If enabled (default is false
) the JPA server will support multitenant queries
+ * If enabled (default is false
) the JPA server will support data partitioning
*
* @since 5.0.0
*/
- public boolean isMultiTenancyEnabled() {
- return myMultiTenancyEnabled;
+ public boolean isPartitioningEnabled() {
+ return myPartitioningEnabled;
}
public enum StoreMetaSourceInformationEnum {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
index ece01089fe6..f693d694b91 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
@@ -50,7 +50,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao suggestKeywords(String theContext, String theSearchParam, String theText, RequestDetails theRequest) {
@@ -282,7 +287,10 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
throw new InvalidRequestException("Invalid context: " + theContext);
}
- ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(contextParts[0], contextParts[1]);
+
+ // FIXME: this method should require a resource type
+ PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, null);
+ ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(partitionId, contextParts[0], contextParts[1]);
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java
index b4752748e78..6bb7b1a69f2 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java
@@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
@@ -37,12 +38,12 @@ import java.util.Set;
public interface ISearchBuilder {
- IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest);
+ IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest, PartitionId thePartitionId);
+
+ Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, PartitionId thePartitionId);
void setMaxResultsToFetch(Integer theMaxResultsToFetch);
- Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest);
-
void loadResourcesByPid(Collection thePids, Collection theIncludedPids, List theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails);
Set loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection theMatches, Set theRevIncludes, boolean theReverseMode,
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
index 891296fba86..2d68d0778a9 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
@@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@@ -165,6 +166,7 @@ public class SearchBuilder implements ISearchBuilder {
private Integer myMaxResultsToFetch;
private Set myPidSet;
private PredicateBuilder myPredicateBuilder;
+ private PartitionId myPartitionId;
/**
* Constructor
@@ -181,7 +183,7 @@ public class SearchBuilder implements ISearchBuilder {
}
private void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List> theAndOrParams, RequestDetails theRequest) {
- myPredicateBuilder.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest);
+ myPredicateBuilder.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, myPartitionId);
}
private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams, RequestDetails theRequest) {
@@ -219,8 +221,8 @@ public class SearchBuilder implements ISearchBuilder {
}
@Override
- public Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest) {
- init(theParams, theSearchUuid);
+ public Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, PartitionId thePartitionId) {
+ init(theParams, theSearchUuid, thePartitionId);
TypedQuery query = createQuery(null, null, true, theRequest);
return new CountQueryIterator(query);
@@ -235,8 +237,8 @@ public class SearchBuilder implements ISearchBuilder {
}
@Override
- public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) {
- init(theParams, theSearchRuntimeDetails.getSearchUuid());
+ public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, PartitionId thePartitionId) {
+ init(theParams, theSearchRuntimeDetails.getSearchUuid(), thePartitionId);
if (myPidSet == null) {
myPidSet = new HashSet<>();
@@ -245,13 +247,16 @@ public class SearchBuilder implements ISearchBuilder {
return new QueryIterator(theSearchRuntimeDetails, theRequest);
}
- private void init(SearchParameterMap theParams, String theSearchUuid) {
+ private void init(SearchParameterMap theParams, String theSearchUuid, PartitionId thePartitionId) {
myParams = theParams;
myCriteriaBuilder = myEntityManager.getCriteriaBuilder();
mySearchUuid = theSearchUuid;
myPredicateBuilder = new PredicateBuilder(this, myPredicateBuilderFactory);
+ myPartitionId = thePartitionId;
}
+
+
private TypedQuery createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) {
CriteriaQuery outerQuery;
/*
@@ -296,7 +301,7 @@ public class SearchBuilder implements ISearchBuilder {
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
- ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myResourceName, idParam.getValue());
+ ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myPartitionId, myResourceName, idParam.getValue());
if (myAlsoIncludePids == null) {
myAlsoIncludePids = new ArrayList<>(1);
}
@@ -352,6 +357,9 @@ public class SearchBuilder implements ISearchBuilder {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
}
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
+ if (myPartitionId != null) {
+ myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myPartitionIdValue"), myPartitionId.getPartitionId()));
+ }
}
// Last updated
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java
index 4e1e2c5a39d..52b6688023e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java
@@ -39,6 +39,9 @@ public interface IForcedIdDao extends JpaRepository {
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id")
Optional findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
+ @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myPartitionId.myPartitionId = :partition_id AND myResourceType = :resource_type AND myForcedId = :forced_id")
+ Optional findByPartitionIdAndTypeAndForcedId(@Param("partition_id") Integer thePartitionId, @Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
+
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java
index c0650d840d6..27814d0e92e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java
@@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends BaseHapiFhirResourceDao resolveResourceIdentity(theResourceType, theId));
+ String key = thePartitionId + "/" + theResourceType + "/" + theId;
+ retVal = myPersistentIdCache.get(key, t -> resolveResourceIdentity(thePartitionId, theResourceType, theId));
}
} else {
@@ -248,12 +251,18 @@ public class IdHelperService {
return typeToIds;
}
- private Long resolveResourceIdentity(String theResourceType, String theId) {
- Long retVal;
- retVal = myForcedIdDao
- .findByTypeAndForcedId(theResourceType, theId)
- .orElseThrow(() -> new ResourceNotFoundException(new IdDt(theResourceType, theId)));
- return retVal;
+ private Long resolveResourceIdentity(@Nullable PartitionId thePartitionId, @Nonnull String theResourceType, @Nonnull String theId) {
+ Optional pid;
+ if (thePartitionId != null) {
+ pid = myForcedIdDao.findByPartitionIdAndTypeAndForcedId(thePartitionId.getPartitionId(), theResourceType, theId);
+ } else {
+ pid = myForcedIdDao.findByTypeAndForcedId(theResourceType, theId);
+ }
+
+ if (pid.isPresent() == false) {
+ throw new ResourceNotFoundException(new IdDt(theResourceType, theId));
+ }
+ return pid.get();
}
private Collection translateForcedIdToPids(RequestDetails theRequest, Collection theId) {
@@ -274,7 +283,7 @@ public class IdHelperService {
if (!pids.isEmpty()) {
myResourceTableDao.findLookupFieldsByResourcePid(pids)
.stream()
- .map(lookup -> new ResourceLookup((String)lookup[0], (Long)lookup[1], (Date)lookup[2]))
+ .map(lookup -> new ResourceLookup((String) lookup[0], (Long) lookup[1], (Date) lookup[2]))
.forEach(retVal::add);
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/partition/RequestPartitionHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/partition/RequestPartitionHelperService.java
new file mode 100644
index 00000000000..b74f438ebf5
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/partition/RequestPartitionHelperService.java
@@ -0,0 +1,101 @@
+package ca.uhn.fhir.jpa.dao.partition;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.interceptor.api.HookParams;
+import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.HashSet;
+
+import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooksAndReturnObject;
+
+public class RequestPartitionHelperService {
+
+ private final HashSet myPartitioningBlacklist;
+
+ @Autowired
+ private DaoConfig myDaoConfig;
+ @Autowired
+ private IInterceptorBroadcaster myInterceptorBroadcaster;
+ @Autowired
+ private FhirContext myFhirContext;
+
+ public RequestPartitionHelperService() {
+ // FIXME: document this list
+
+ myPartitioningBlacklist = new HashSet<>();
+
+ // Infrastructure
+ myPartitioningBlacklist.add("Subscription");
+ myPartitioningBlacklist.add("SearchParameter");
+
+ // Validation
+ myPartitioningBlacklist.add("StructureDefinition");
+ myPartitioningBlacklist.add("Questionnaire");
+
+ // Terminology
+ myPartitioningBlacklist.add("ConceptMap");
+ myPartitioningBlacklist.add("CodeSystem");
+ myPartitioningBlacklist.add("ValueSet");
+ }
+
+ /**
+ * Invoke the STORAGE_PARTITION_IDENTIFY_READ
interceptor pointcut to determine the tenant for a read request
+ */
+ @Nullable
+ public PartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType) {
+ PartitionId partitionId = null;
+
+ if (myDaoConfig.isPartitioningEnabled()) {
+ // Interceptor call: STORAGE_PARTITION_IDENTIFY_READ
+ HookParams params = new HookParams()
+ .add(RequestDetails.class, theRequest)
+ .addIfMatchesType(ServletRequestDetails.class, theRequest);
+ partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_READ, params);
+
+ validatePartition(partitionId, theResourceType);
+ }
+
+ return partitionId;
+ }
+
+ /**
+ * Invoke the STORAGE_PARTITION_IDENTIFY_CREATE
interceptor pointcut to determine the tenant for a read request
+ */
+ @Nullable
+ public PartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource) {
+
+ PartitionId partitionId = null;
+ if (myDaoConfig.isPartitioningEnabled()) {
+ // Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE
+ HookParams params = new HookParams()
+ .add(IBaseResource.class, theResource)
+ .add(RequestDetails.class, theRequest)
+ .addIfMatchesType(ServletRequestDetails.class, theRequest);
+ partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
+
+ String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
+ validatePartition(partitionId, resourceName);
+ }
+
+ return partitionId;
+ }
+
+ private void validatePartition(@Nullable PartitionId thePartitionId, @Nonnull String theResourceName) {
+ if (thePartitionId != null) {
+ if (myPartitioningBlacklist.contains(theResourceName)) {
+ String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "blacklistedResourceTypeForPartitioning", theResourceName);
+ throw new InvalidRequestException(msg);
+ }
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
index e37dab1e1ac..ed5f8a344d0 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
@@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
@@ -125,8 +126,10 @@ abstract class BasePredicateBuilder {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(hashPresence, hash));
}
- void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join theJoin) {
-
+ void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join theJoin, PartitionId thePartitionId) {
+ if (thePartitionId != null) {
+ myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), thePartitionId.getPartitionId()));
+ }
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java
index f7cd30be947..9bce6b7020c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java
@@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
* #L%
*/
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.model.api.IQueryParameterType;
import javax.annotation.Nullable;
@@ -31,5 +32,6 @@ public interface IPredicateBuilder {
Predicate addPredicate(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList,
- SearchFilterParser.CompareOperation operation);
+ SearchFilterParser.CompareOperation operation,
+ PartitionId thePartitionId);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
index 1de807105f4..7e88d5de53b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
@@ -91,24 +91,24 @@ public class PredicateBuilder {
return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation);
}
- public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List> theAndOrParams, RequestDetails theRequest) {
- myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest);
+ public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List> theAndOrParams, RequestDetails theRequest, PartitionId thePartitionId) {
+ myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, thePartitionId);
}
- Subquery createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList theOrValues, RequestDetails theRequest) {
- return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest);
+ Subquery createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList theOrValues, RequestDetails theRequest, PartitionId thePartitionId) {
+ return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest, thePartitionId);
}
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join theJoin) {
return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin);
}
- void addPredicateResourceId(List> theAndOrParams, String theResourceName, RequestDetails theRequest) {
- myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, theRequest);
+ void addPredicateResourceId(List> theAndOrParams, String theResourceName, PartitionId thePartitionId) {
+ myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, thePartitionId);
}
- public Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
- return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequest);
+ public Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
+ return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, thePartitionId);
}
Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theStringJoin) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
index 1a71242f249..83141850610 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
@@ -38,6 +38,7 @@ import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
@@ -98,7 +99,7 @@ import static org.apache.commons.lang3.StringUtils.trim;
@Scope("prototype")
class PredicateBuilderReference extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderReference.class);
-
+ private final PredicateBuilder myPredicateBuilder;
@Autowired
IdHelperService myIdHelperService;
@Autowired
@@ -110,8 +111,6 @@ class PredicateBuilderReference extends BasePredicateBuilder {
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
- private final PredicateBuilder myPredicateBuilder;
-
PredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
super(theSearchBuilder);
myPredicateBuilder = thePredicateBuilder;
@@ -125,7 +124,8 @@ class PredicateBuilderReference extends BasePredicateBuilder {
String theParamName,
List extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation,
- RequestDetails theRequest) {
+ RequestDetails theRequest,
+ PartitionId thePartitionId) {
//Is this just to ensure the chain has been split correctly???
assert theParamName.contains(".") == false;
@@ -177,7 +177,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
* Handle chained search, e.g. Patient?organization.name=Kwik-e-mart
*/
- return addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest);
+ return addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest, thePartitionId);
}
@@ -243,7 +243,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
* This is for handling queries like the following: /Observation?device.identifier=urn:system|foo in which we use a chain
* on the device.
*/
- private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List extends IQueryParameterType> theList, Join theJoin, List theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest) {
+ private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List extends IQueryParameterType> theList, Join theJoin, List theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest, PartitionId thePartitionId) {
final List> resourceTypes;
if (!theReferenceParam.hasResourceType()) {
@@ -384,7 +384,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
- Subquery subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest);
+ Subquery subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest, thePartitionId);
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin);
Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ);
@@ -410,9 +410,9 @@ class PredicateBuilderReference extends BasePredicateBuilder {
String message = new StringBuilder()
.append("This search uses an unqualified resource(a parameter in a chain without a resource type). ")
.append("This is less efficient than using a qualified type. ")
- .append("[" + theParamName + "] resolves to ["+ theCandidateTargetTypes.stream().map(Class::getSimpleName).collect(Collectors.joining(",")) +"].")
+ .append("[" + theParamName + "] resolves to [" + theCandidateTargetTypes.stream().map(Class::getSimpleName).collect(Collectors.joining(",")) + "].")
.append("If you know what you're looking for, try qualifying it like this: ")
- .append(theCandidateTargetTypes.stream().map(cls -> "[" +cls.getSimpleName() +":"+theParamName+"]").collect(Collectors.joining(" or ")))
+ .append(theCandidateTargetTypes.stream().map(cls -> "[" + cls.getSimpleName() + ":" + theParamName + "]").collect(Collectors.joining(" or ")))
.toString();
StorageProcessingMessage msg = new StorageProcessingMessage()
.setMessage(message);
@@ -472,7 +472,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return chainValue;
}
- Subquery createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List theOrValues, RequestDetails theRequest) {
+ Subquery createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List theOrValues, RequestDetails theRequest, PartitionId thePartitionId) {
Subquery subQ = myQueryRoot.subquery(Long.class);
/*
* We're doing a chain call, so push the current query root
@@ -490,7 +490,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
if (theFoundChainMatch) {
- searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest);
+ searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest, thePartitionId);
subQ.where(myQueryRoot.getPredicateArray());
}
@@ -501,7 +501,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return subQ;
}
- void searchForIdsWithAndOr(String theResourceName, String theParamName, List> theAndOrParams, RequestDetails theRequest) {
+ void searchForIdsWithAndOr(String theResourceName, String theParamName, List> theAndOrParams, RequestDetails theRequest, PartitionId thePartitionId) {
if (theAndOrParams.isEmpty()) {
return;
@@ -509,7 +509,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
switch (theParamName) {
case IAnyResource.SP_RES_ID:
- myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, theRequest);
+ myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, thePartitionId);
break;
case IAnyResource.SP_RES_LANGUAGE:
@@ -518,7 +518,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
break;
case Constants.PARAM_HAS:
- addPredicateHas(theResourceName, theAndOrParams, theRequest);
+ addPredicateHas(theResourceName, theAndOrParams, theRequest, thePartitionId);
break;
case Constants.PARAM_TAG:
@@ -548,7 +548,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
break;
case REFERENCE:
for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
- addPredicate(theResourceName, theParamName, nextAnd, null, theRequest);
+ addPredicate(theResourceName, theParamName, nextAnd, null, theRequest, thePartitionId);
}
break;
case STRING:
@@ -613,7 +613,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
// point to do something more fancy...
ArrayList holdPredicates = new ArrayList<>(myQueryRoot.getPredicates());
- Predicate filterPredicate = processFilter(filter, theResourceName, theRequest);
+ Predicate filterPredicate = processFilter(filter, theResourceName, theRequest, thePartitionId);
myQueryRoot.clearPredicates();
myQueryRoot.addPredicates(holdPredicates);
myQueryRoot.addPredicate(filterPredicate);
@@ -629,20 +629,17 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
}
- private Predicate processFilter(SearchFilterParser.Filter theFilter,
- String theResourceName, RequestDetails theRequest) {
+ private Predicate processFilter(SearchFilterParser.Filter theFilter, String theResourceName, RequestDetails theRequest, PartitionId thePartitionId) {
if (theFilter instanceof SearchFilterParser.FilterParameter) {
return processFilterParameter((SearchFilterParser.FilterParameter) theFilter,
- theResourceName, theRequest);
+ theResourceName, theRequest, thePartitionId);
} else if (theFilter instanceof SearchFilterParser.FilterLogical) {
// Left side
- Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(),
- theResourceName, theRequest);
+ Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(), theResourceName, theRequest, thePartitionId);
// Right side
- Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(),
- theResourceName, theRequest);
+ Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(), theResourceName, theRequest, thePartitionId);
if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) {
return myCriteriaBuilder.and(xPredicate, yPredicate);
@@ -650,14 +647,13 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return myCriteriaBuilder.or(xPredicate, yPredicate);
}
} else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) {
- return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(),
- theResourceName, theRequest);
+ return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(), theResourceName, theRequest, thePartitionId);
}
return null;
}
private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter,
- String theResourceName, RequestDetails theRequest) {
+ String theResourceName, RequestDetails theRequest, PartitionId thePartitionId) {
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, theFilter.getParamPath().getName());
@@ -670,7 +666,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
null,
null,
theFilter.getValue());
- return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), theRequest);
+ return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), thePartitionId);
} else {
throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search");
}
@@ -706,7 +702,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
String chain = (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() : null;
String value = theFilter.getValue();
ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value);
- return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest);
+ return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, thePartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) {
return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation());
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
@@ -794,7 +790,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
continue;
}
- Predicate predicate = null;
+ Predicate predicate;
if ((operation == null) ||
(operation == SearchFilterParser.CompareOperation.eq)) {
predicate = myQueryRoot.get("myLanguage").as(String.class).in(values);
@@ -819,6 +815,9 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
private Predicate addPredicateSource(List extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
+ // Required for now
+ assert theOperation == SearchFilterParser.CompareOperation.eq;
+
if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) {
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled");
throw new InvalidRequestException(msg);
@@ -848,7 +847,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return retVal;
}
- private void addPredicateHas(String theResourceType, List> theHasParameters, RequestDetails theRequest) {
+ private void addPredicateHas(String theResourceType, List> theHasParameters, RequestDetails theRequest,PartitionId thePartitionId) {
for (List extends IQueryParameterType> nextOrList : theHasParameters) {
@@ -878,8 +877,6 @@ class PredicateBuilderReference extends BasePredicateBuilder {
throw new InvalidRequestException("Invalid resource type: " + targetResourceType);
}
- assert parameterName != null;
-
//Ensure that the name of the search param
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
// exists on the target resource type.
@@ -909,11 +906,11 @@ class PredicateBuilderReference extends BasePredicateBuilder {
String chainedPartOfParameter = getChainedPart(parameterName);
orValues.stream()
.filter(qp -> qp instanceof ReferenceParam)
- .map(qp -> (ReferenceParam)qp)
+ .map(qp -> (ReferenceParam) qp)
.forEach(rp -> rp.setChain(getChainedPart(chainedPartOfParameter)));
}
- Subquery subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest);
+ Subquery subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest, thePartitionId);
Join join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java
index 137b86cfd18..99e83422d7d 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java
@@ -23,9 +23,9 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
-import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.r4.model.IdType;
import org.slf4j.Logger;
@@ -58,9 +58,9 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
}
@Nullable
- Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
+ Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
- Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation);
+ Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation, thePartitionId);
if (nextPredicate != null) {
myQueryRoot.addPredicate(nextPredicate);
@@ -71,7 +71,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
}
@Nullable
- private Predicate createPredicate(Root theRoot, String theResourceName, List> theValues, SearchFilterParser.CompareOperation theOperation) {
+ private Predicate createPredicate(Root theRoot, String theResourceName, List> theValues, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
Predicate nextPredicate = null;
Set allOrPids = null;
@@ -89,7 +89,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
if (isNotBlank(value)) {
haveValue = true;
try {
- ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theResourceName, valueAsId.getIdPart());
+ ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(thePartitionId, theResourceName, valueAsId.getIdPart());
orPids.add(pid);
} catch (ResourceNotFoundException e) {
// This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
index cb94fa7fe2e..d4814b6617b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
@@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
@@ -55,16 +56,22 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
public Predicate addPredicate(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList,
- SearchFilterParser.CompareOperation operation) {
+ SearchFilterParser.CompareOperation operation,
+ PartitionId thePartitionId) {
Join join = createJoin(SearchBuilderJoinEnum.URI, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
+ addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
List codePredicates = new ArrayList<>();
+ if (thePartitionId != null) {
+ Predicate partitionPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
+ codePredicates.add(partitionPredicate);
+ }
+
for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof UriParam) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java
index c51ec3f4461..277685b1a03 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java
@@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao getResources(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails);
- IBundleProvider registerSearch(IFhirResourceDao theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, @Nullable RequestDetails theRequestDetails);
+ IBundleProvider registerSearch(IFhirResourceDao> theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, @Nullable RequestDetails theRequestDetails);
/**
* Fetch the total number of search results for the given currently executing search, if one is currently executing and
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java
index 7125a415277..9f5b786fb97 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java
@@ -25,11 +25,13 @@ import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.*;
+import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchInclude;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
@@ -246,7 +248,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
String resourceType = search.getResourceType();
SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search"));
IFhirResourceDao> resourceDao = myDaoRegistry.getResourceDao(resourceType);
- SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails);
+ PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, resourceType);
+ SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails, partitionId);
myIdToSearchTask.put(search.getUuid(), task);
myExecutor.submit(task);
}
@@ -284,8 +287,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
theRetVal.setInterceptorBroadcaster(myInterceptorBroadcaster);
}
+ @Autowired
+ private RequestPartitionHelperService myRequestPartitionHelperService;
+
@Override
- public IBundleProvider registerSearch(final IFhirResourceDao theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, RequestDetails theRequestDetails) {
+ public IBundleProvider registerSearch(final IFhirResourceDao> theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, RequestDetails theRequestDetails) {
final String searchUuid = UUID.randomUUID().toString();
ourLog.debug("Registering new search {}", searchUuid);
@@ -298,7 +304,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null) {
ourLog.debug("Search {} is loading in synchronous mode", searchUuid);
- return executeQuery(theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo);
+ return executeQuery(theResourceType, theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo);
}
/*
@@ -369,7 +375,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
- SearchTask task = new SearchTask(search, theCallingDao, theParams, theResourceType, theRequestDetails);
+ PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theResourceType);
+
+ SearchTask task = new SearchTask(search, theCallingDao, theParams, theResourceType, theRequestDetails, partitionId);
myIdToSearchTask.put(search.getUuid(), task);
myExecutor.submit(task);
@@ -380,7 +388,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
return retVal;
}
- @org.jetbrains.annotations.Nullable
+ @Nullable
private IBundleProvider findCachedQuery(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString) {
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
PersistedJpaBundleProvider foundSearchProvider = txTemplate.execute(t -> {
@@ -416,10 +424,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
return retVal;
});
- if (foundSearchProvider != null) {
- return foundSearchProvider;
- }
- return null;
+ // May be null
+ return foundSearchProvider;
}
@Nullable
@@ -441,7 +447,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
return searchToUse;
}
- private IBundleProvider executeQuery(SearchParameterMap theParams, RequestDetails theRequestDetails, String theSearchUuid, ISearchBuilder theSb, Integer theLoadSynchronousUpTo) {
+ private IBundleProvider executeQuery(String theResourceType, SearchParameterMap theParams, RequestDetails theRequestDetails, String theSearchUuid, ISearchBuilder theSb, Integer theLoadSynchronousUpTo) {
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequestDetails, theSearchUuid);
searchRuntimeDetails.setLoadSynchronous(true);
@@ -453,7 +459,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
// Load the results synchronously
final List pids = new ArrayList<>();
- try (IResultIterator resultIter = theSb.createQuery(theParams, searchRuntimeDetails, theRequestDetails)) {
+ PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theResourceType);
+ try (IResultIterator resultIter = theSb.createQuery(theParams, searchRuntimeDetails, theRequestDetails, partitionId)) {
while (resultIter.hasNext()) {
pids.add(resultIter.next());
if (theLoadSynchronousUpTo != null && pids.size() >= theLoadSynchronousUpTo) {
@@ -595,6 +602,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
private final CountDownLatch myCompletionLatch;
private final ArrayList myUnsyncedPids = new ArrayList<>();
private final RequestDetails myRequest;
+ private final PartitionId myPartitionId;
private Search mySearch;
private boolean myAbortRequested;
private int myCountSavedTotal = 0;
@@ -605,10 +613,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
private Integer myMaxResultsToFetch;
private SearchRuntimeDetails mySearchRuntimeDetails;
private Transaction myParentTransaction;
+
/**
* Constructor
*/
- protected SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest) {
+ protected SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, PartitionId thePartitionId) {
mySearch = theSearch;
myCallingDao = theCallingDao;
myParams = theParams;
@@ -616,6 +625,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
myCompletionLatch = new CountDownLatch(1);
mySearchRuntimeDetails = new SearchRuntimeDetails(theRequest, mySearch.getUuid());
mySearchRuntimeDetails.setQueryString(theParams.toNormalizedQueryString(theCallingDao.getContext()));
+ myPartitionId = thePartitionId;
myRequest = theRequest;
myParentTransaction = ElasticApm.currentTransaction();
}
@@ -977,7 +987,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
if (wantCount) {
ourLog.trace("Performing count");
ISearchBuilder sb = newSearchBuilder();
- Iterator countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest);
+ Iterator countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest, myPartitionId);
Long count = countIterator.next();
ourLog.trace("Got count {}", count);
@@ -1053,7 +1063,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
/*
* Construct the SQL query we'll be sending to the database
*/
- try (IResultIterator resultIterator = sb.createQuery(myParams, mySearchRuntimeDetails, myRequest)) {
+ try (IResultIterator resultIterator = sb.createQuery(myParams, mySearchRuntimeDetails, myRequest, myPartitionId)) {
assert (resultIterator != null);
/*
@@ -1105,8 +1115,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
public class SearchContinuationTask extends SearchTask {
- public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest) {
- super(theSearch, theCallingDao, theParams, theResourceType, theRequest);
+ public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, PartitionId thePartitionId) {
+ super(theSearch, theCallingDao, theParams, theResourceType, theRequest, thePartitionId);
}
@Override
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java
index d37f27819fb..80050d67e03 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java
@@ -66,7 +66,7 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
present.setResource(theResource);
present.setParamName(paramName);
present.setPresent(next.getValue());
- present.setTenantId(theResource.getTenantId());
+ present.setPartitionId(theResource.getPartitionId());
present.calculateHashes();
newHashToPresence.put(present.getHashPresence(), present);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java
index fc6cc291f4e..efb39b1d525 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java
@@ -117,7 +117,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
@Override
public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) {
- return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart());
+ return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart());
}
@Transactional
@@ -291,7 +291,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource);
- ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(csId.getResourceType(), csId.getIdPart());
+ ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(null, csId.getResourceType(), csId.getIdPart());
ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong());
ourLog.info("CodeSystem resource has ID: {}", csId.getValue());
@@ -551,7 +551,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) {
- return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart());
+ return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart());
}
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap theConceptsStack, int theTotalConcepts) {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java
index 9c098d3c03f..183a6ce6c20 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java
@@ -280,6 +280,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Autowired
protected IResourceTagDao myResourceTagDao;
@Autowired
+ protected IResourceHistoryTagDao myResourceHistoryTagDao;
+ @Autowired
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
@Autowired(required = false)
protected IFulltextSearchSvc mySearchDao;
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
deleted file mode 100644
index d77a5e755d1..00000000000
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/MultitenantR4Test.java
+++ /dev/null
@@ -1,334 +0,0 @@
-package ca.uhn.fhir.jpa.dao.r4;
-
-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;
-import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
-import ca.uhn.fhir.jpa.model.entity.TenantId;
-import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
-import ca.uhn.fhir.util.TestUtil;
-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;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.Test;
-
-import javax.servlet.ServletException;
-import java.time.LocalDate;
-import java.time.Month;
-import java.util.ArrayList;
-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;
-
-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() {
- myDaoConfig.setMultiTenancyEnabled(new DaoConfig().isMultiTenancyEnabled());
-
- myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
- myInterceptor = null;
- }
-
- @Override
- @Before
- public void before() throws ServletException {
- super.before();
-
- myDaoConfig.setMultiTenancyEnabled(true);
- myDaoConfig.setUniqueIndexesEnabled(true);
- myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
-
- myTenantDate = LocalDate.of(2020, Month.JANUARY, 14);
- myTenantId = 3;
- }
-
-
- @Test
- public void testCreateResourceNoTenant() {
- Patient p = new Patient();
- p.addIdentifier().setSystem("system").setValue("value");
- p.setBirthDate(new Date());
- Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
-
- runInTransaction(() -> {
- ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
- assertNull(resourceTable.getTenantId());
- });
- }
-
-
- @Test
- public void testCreateResourceWithTenant() {
- createUniqueCompositeSp();
- createRequestId();
-
- addCreateTenant(myTenantId, myTenantDate);
- addCreateTenant(myTenantId, myTenantDate);
-
- Organization org = new Organization();
- org.setName("org");
- IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
-
- Patient p = new Patient();
- p.getMeta().addTag("http://system", "code", "diisplay");
- p.addName().setFamily("FAM");
- p.addIdentifier().setSystem("system").setValue("value");
- p.setBirthDate(new Date());
- p.getManagingOrganization().setReferenceElement(orgId);
- Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
-
- 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 version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
- 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(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(myTenantId, resourceLinks.get(0).getTenantId().getTenantId().intValue());
- assertEquals(myTenantDate, resourceLinks.get(0).getTenantId().getTenantDate());
-
- // HFJ_RES_PARAM_PRESENT
- List presents = mySearchParamPresentDao.findAllForResource(resourceTable);
- 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(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() {
- 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, 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
- 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");
- sp.setType(Enumerations.SearchParamType.DATE);
- sp.setCode("birthdate");
- sp.setExpression("Patient.birthDate");
- sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
- sp.addBase("Patient");
- mySearchParameterDao.update(sp);
-
- sp = new SearchParameter();
- sp.setId("SearchParameter/patient-birthdate-unique");
- sp.setType(Enumerations.SearchParamType.COMPOSITE);
- sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
- sp.addBase("Patient");
- sp.addComponent()
- .setExpression("Patient")
- .setDefinition("SearchParameter/patient-birthdate");
- sp.addExtension()
- .setUrl(SearchParamConstants.EXT_SP_UNIQUE)
- .setValue(new BooleanType(true));
- mySearchParameterDao.update(sp);
-
- mySearchParamRegistry.forceRefresh();
- }
-
-
-
- private void addCreateTenant(int theTenantId, LocalDate theTenantDate) {
- if (myTenantInterceptor == null) {
- myTenantInterceptor = new MyInterceptor();
- myInterceptorRegistry.registerInterceptor(myTenantInterceptor);
- }
- 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 myCreateTenantIds = new ArrayList<>();
-
- public void addCreateTenant(TenantId theTenantId) {
- Validate.notNull(theTenantId);
- myCreateTenantIds.add(theTenantId);
- }
-
- @Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE)
- public TenantId tenantIdentifyCreate() {
- TenantId retVal = myCreateTenantIds.remove(0);
- ourLog.info("Returning tenant ID: {}", retVal);
- return retVal;
- }
-
- }
-
- @AfterClass
- public static void afterClassClearContext() {
- TestUtil.clearAllStaticFieldsForUnitTest();
- }
-
-}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
new file mode 100644
index 00000000000..bf63a643bf9
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
@@ -0,0 +1,499 @@
+package ca.uhn.fhir.jpa.dao.r4;
+
+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.*;
+import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.util.TestUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.hamcrest.Matchers;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+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;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.ServletException;
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+@SuppressWarnings("unchecked")
+public class PartitioningR4Test extends BaseJpaR4SystemTest {
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PartitioningR4Test.class);
+ private MyInterceptor myTenantInterceptor;
+ private LocalDate myTenantDate;
+ private int myPartitionId;
+
+ @After
+ public void after() {
+ myTenantInterceptor.assertNoRemainingIds();
+
+ myDaoConfig.setPartitioningEnabled(new DaoConfig().isPartitioningEnabled());
+
+ myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
+ myInterceptor = null;
+ }
+
+ @Override
+ @Before
+ public void before() throws ServletException {
+ super.before();
+
+ myDaoConfig.setPartitioningEnabled(true);
+ myDaoConfig.setUniqueIndexesEnabled(true);
+ myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
+
+ myTenantDate = LocalDate.of(2020, Month.JANUARY, 14);
+ myPartitionId = 3;
+
+ myTenantInterceptor = new MyInterceptor();
+ }
+
+
+ @Test
+ public void testCreateResourceNoTenant() {
+ Patient p = new Patient();
+ p.addIdentifier().setSystem("system").setValue("value");
+ p.setBirthDate(new Date());
+ Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
+
+ runInTransaction(() -> {
+ ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
+ assertNull(resourceTable.getPartitionId());
+ });
+ }
+
+
+ @Test
+ public void testCreateResourceWithTenant() {
+ createUniqueCompositeSp();
+ createRequestId();
+
+ addCreateTenant(myPartitionId, myTenantDate);
+ addCreateTenant(myPartitionId, myTenantDate);
+
+ Organization org = new Organization();
+ org.setName("org");
+ IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
+
+ Patient p = new Patient();
+ p.getMeta().addTag("http://system", "code", "diisplay");
+ p.addName().setFamily("FAM");
+ p.addIdentifier().setSystem("system").setValue("value");
+ p.setBirthDate(new Date());
+ p.getManagingOrganization().setReferenceElement(orgId);
+ Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
+
+ runInTransaction(() -> {
+ // HFJ_RESOURCE
+ ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
+ assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
+
+ // HFJ_RES_TAG
+ List tags = myResourceTagDao.findAll();
+ assertEquals(1, tags.size());
+ assertEquals(myPartitionId, tags.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, tags.get(0).getPartitionId().getPartitionDate());
+
+ // HFJ_RES_VER
+ ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
+ assertEquals(myPartitionId, version.getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, version.getPartitionId().getPartitionDate());
+
+ // HFJ_HISTORY_TAG
+ List historyTags = myResourceHistoryTagDao.findAll();
+ assertEquals(1, historyTags.size());
+ assertEquals(myPartitionId, historyTags.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, historyTags.get(0).getPartitionId().getPartitionDate());
+
+ // HFJ_RES_VER_PROV
+ assertNotNull(version.getProvenance());
+ assertEquals(myPartitionId, version.getProvenance().getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, version.getProvenance().getPartitionId().getPartitionDate());
+
+ // 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(myPartitionId, strings.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, strings.get(0).getPartitionId().getPartitionDate());
+
+ // 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(myPartitionId, dates.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, dates.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionId, dates.get(1).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, dates.get(1).getPartitionId().getPartitionDate());
+
+ // HFJ_RES_LINK
+ List resourceLinks = myResourceLinkDao.findAllForResourceId(patientId);
+ assertEquals(1, resourceLinks.size());
+ assertEquals(myPartitionId, resourceLinks.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, resourceLinks.get(0).getPartitionId().getPartitionDate());
+
+ // HFJ_RES_PARAM_PRESENT
+ List presents = mySearchParamPresentDao.findAllForResource(resourceTable);
+ assertEquals(myPartitionId, presents.size());
+ assertEquals(myPartitionId, presents.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, presents.get(0).getPartitionId().getPartitionDate());
+
+ // HFJ_IDX_CMP_STRING_UNIQ
+ List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId);
+ assertEquals(1, uniques.size());
+ assertEquals(myPartitionId, uniques.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, uniques.get(0).getPartitionId().getPartitionDate());
+ });
+
+ }
+
+ @Test
+ public void testCreateWithForcedId() {
+ addCreateTenant(myPartitionId, myTenantDate);
+ addCreateTenant(myPartitionId, 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(myPartitionId, forcedIds.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, forcedIds.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionId, forcedIds.get(1).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, forcedIds.get(1).getPartitionId().getPartitionDate());
+ });
+
+ }
+
+ @Test
+ public void testUpdateResourceWithTenant() {
+ createRequestId();
+ addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
+ addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
+
+ // Create a resource
+ Patient p = new Patient();
+ p.getMeta().addTag("http://system", "code", "diisplay");
+ p.setActive(true);
+ Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
+ runInTransaction(() -> {
+ // HFJ_RESOURCE
+ ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
+ assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
+ });
+
+ // Update that resource
+ p = new Patient();
+ p.setId("Patient/" + patientId);
+ p.setActive(false);
+ myPatientDao.update(p, mySrd);
+
+ runInTransaction(() -> {
+ // HFJ_RESOURCE
+ ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
+ assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
+
+ // HFJ_RES_VER
+ int version = 2;
+ ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, version);
+ assertEquals(myPartitionId, resVer.getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, resVer.getPartitionId().getPartitionDate());
+
+ // HFJ_HISTORY_TAG
+ List historyTags = myResourceHistoryTagDao.findAll();
+ assertEquals(2, historyTags.size());
+ assertEquals(myPartitionId, historyTags.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, historyTags.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionId, historyTags.get(1).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, historyTags.get(1).getPartitionId().getPartitionDate());
+
+ // HFJ_RES_VER_PROV
+ assertNotNull(resVer.getProvenance());
+ assertNotNull(resVer.getPartitionId());
+ assertEquals(myPartitionId, resVer.getProvenance().getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, resVer.getProvenance().getPartitionId().getPartitionDate());
+
+ // 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(myPartitionId, strings.get(0).getPartitionId().getPartitionId().intValue());
+ assertEquals(myTenantDate, strings.get(0).getPartitionId().getPartitionDate());
+
+ });
+
+ }
+
+ @Test
+ public void testReadAcrossTenants() {
+ IIdType patientId1 = createPatient(1, withActiveTrue());
+ IIdType patientId2 = createPatient(2, withActiveTrue());
+
+ addReadTenant(null);
+ IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
+ assertEquals(patientId1, gotId1);
+
+ addReadTenant(null);
+ IdType gotId2 = myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
+ assertEquals(patientId2, gotId2);
+ }
+
+ @Test
+ public void testReadSpecificTenant_PidId() {
+ IIdType patientIdNull = createPatient(null, withActiveTrue());
+ IIdType patientId1 = createPatient(1, withActiveTrue());
+ IIdType patientId2 = createPatient(2, withActiveTrue());
+
+ // Read in correct tenant
+ addReadTenant(1);
+ IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
+ assertEquals(patientId1, gotId1);
+
+ // Read in null tenant
+ addReadTenant(1);
+ try {
+ myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless();
+ fail();
+ } catch (ResourceNotFoundException e) {
+ assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
+ }
+
+ // Read in wrong tenant
+ addReadTenant(1);
+ try {
+ myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
+ fail();
+ } catch (ResourceNotFoundException e) {
+ assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
+ }
+ }
+
+ @Test
+ public void testReadSpecificTenant_ForcedId() {
+ IIdType patientIdNull = createPatient(null, withActiveTrue(), withId("NULL"));
+ IIdType patientId1 = createPatient(1, withActiveTrue(), withId("ONE"));
+ IIdType patientId2 = createPatient(2, withActiveTrue(), withId("TWO"));
+
+ // Read in correct tenant
+ addReadTenant(1);
+ IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
+ assertEquals(patientId1, gotId1);
+
+ // Read in null tenant
+ addReadTenant(1);
+ try {
+ myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless();
+ fail();
+ } catch (ResourceNotFoundException e) {
+ assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
+ }
+
+ // Read in wrong tenant
+ addReadTenant(1);
+ try {
+ myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
+ fail();
+ } catch (ResourceNotFoundException e) {
+ assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
+ }
+ }
+
+ @Test
+ public void testSearch_NoParams_SearchAllTenants() {
+ IIdType patientIdNull = createPatient(null, withActiveTrue());
+ IIdType patientId1 = createPatient(1, withActiveTrue());
+ IIdType patientId2 = createPatient(2, withActiveTrue());
+
+ addReadTenant(null);
+
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ }
+
+ @Test
+ public void testSearch_NoParams_SearchOneTenant() {
+ createPatient(null, withActiveTrue());
+ IIdType patientId1 = createPatient(1, withActiveTrue());
+ createPatient(2, withActiveTrue());
+
+ addReadTenant(1);
+
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ assertThat(ids, Matchers.contains(patientId1));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ }
+
+ private void createUniqueCompositeSp() {
+ 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");
+ mySearchParameterDao.update(sp);
+
+ sp = new SearchParameter();
+ sp.setId("SearchParameter/patient-birthdate-unique");
+ sp.setType(Enumerations.SearchParamType.COMPOSITE);
+ sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
+ sp.addBase("Patient");
+ sp.addComponent()
+ .setExpression("Patient")
+ .setDefinition("SearchParameter/patient-birthdate");
+ sp.addExtension()
+ .setUrl(SearchParamConstants.EXT_SP_UNIQUE)
+ .setValue(new BooleanType(true));
+ mySearchParameterDao.update(sp);
+
+ mySearchParamRegistry.forceRefresh();
+ }
+
+
+ private void addCreateTenant(int thePartitionId, LocalDate theTenantDate) {
+ registerInterceptorIfNeeded();
+ myTenantInterceptor.addCreateTenant(new PartitionId(thePartitionId, theTenantDate));
+ }
+
+ private void addReadTenant(Integer thePartitionId) {
+ registerInterceptorIfNeeded();
+ PartitionId partitionId = null;
+ if (thePartitionId != null) {
+ partitionId = new PartitionId(thePartitionId, null);
+ }
+ myTenantInterceptor.addReadTenant(partitionId);
+ }
+
+ private void registerInterceptorIfNeeded() {
+ if (!myInterceptorRegistry.getAllRegisteredInterceptors().contains(myTenantInterceptor)) {
+ myInterceptorRegistry.registerInterceptor(myTenantInterceptor);
+ }
+ }
+
+ public IIdType createPatient(Integer thePartitionId, Consumer... theModifiers) {
+ if (thePartitionId != null) {
+ addCreateTenant(thePartitionId, null);
+ }
+
+ Patient p = new Patient();
+ for (Consumer next : theModifiers) {
+ next.accept(p);
+ }
+
+ return myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
+ }
+
+ public void createRequestId() {
+ when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
+ }
+
+ private Consumer withActiveTrue() {
+ return t -> t.setActive(true);
+ }
+
+ private Consumer withId(String theId) {
+ return t -> {
+ assertThat(theId, matchesPattern("[a-zA-Z0-9]+"));
+ t.setId("Patient/" + theId);
+ };
+ }
+
+ @Interceptor
+ public static class MyInterceptor {
+
+
+ private final List myCreatePartitionIds = new ArrayList<>();
+ private final List myReadPartitionIds = new ArrayList<>();
+
+ public void addCreateTenant(PartitionId thePartitionId) {
+ Validate.notNull(thePartitionId);
+ myCreatePartitionIds.add(thePartitionId);
+ }
+
+ public void addReadTenant(PartitionId thePartitionId) {
+ myReadPartitionIds.add(thePartitionId);
+ }
+
+ @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE)
+ public PartitionId tenantIdentifyCreate(IBaseResource theResource, ServletRequestDetails theRequestDetails) {
+ assertNotNull(theResource);
+ PartitionId retVal = myCreatePartitionIds.remove(0);
+ ourLog.info("Returning partition for create: {}", retVal);
+ return retVal;
+ }
+
+ @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
+ public PartitionId tenantIdentifyRead(ServletRequestDetails theRequestDetails) {
+ PartitionId retVal = myReadPartitionIds.remove(0);
+ ourLog.info("Returning partition for read: {}", retVal);
+ return retVal;
+ }
+
+ public void assertNoRemainingIds() {
+ assertEquals(0, myCreatePartitionIds.size());
+ assertEquals(0, myReadPartitionIds.size());
+ }
+ }
+
+ @AfterClass
+ public static void afterClassClearContext() {
+ TestUtil.clearAllStaticFieldsForUnitTest();
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java
index 6fdbfb78500..a70990bce7c 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java
@@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
@@ -147,7 +148,7 @@ public class SearchCoordinatorSvcImplTest {
List pids = createPidSequence(800);
IResultIterator iter = new FailAfterNIterator(new SlowIterator(pids.iterator(), 2), 300);
- when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter);
+ when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null);
assertNotNull(result.getUuid());
@@ -180,7 +181,7 @@ public class SearchCoordinatorSvcImplTest {
List pids = createPidSequence(800);
SlowIterator iter = new SlowIterator(pids.iterator(), 1);
- when(mySearchBuilder.createQuery(any(), any(), any())).thenReturn(iter);
+ when(mySearchBuilder.createQuery(any(), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
when(mySearchCacheSvc.save(any())).thenAnswer(t -> {
@@ -262,7 +263,7 @@ public class SearchCoordinatorSvcImplTest {
List pids = createPidSequence(800);
SlowIterator iter = new SlowIterator(pids.iterator(), 2);
- when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter);
+ when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
@@ -286,7 +287,7 @@ public class SearchCoordinatorSvcImplTest {
List pids = createPidSequence(800);
SlowIterator iter = new SlowIterator(pids.iterator(), 500);
- when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter);
+ when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null);
assertNotNull(result.getUuid());
@@ -331,7 +332,7 @@ public class SearchCoordinatorSvcImplTest {
List pids = createPidSequence(800);
IResultIterator iter = new SlowIterator(pids.iterator(), 2);
- when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter);
+ when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
when(mySearchCacheSvc.save(any())).thenAnswer(t ->{
ourLog.info("Saving search");
return t.getArgument( 0, Search.class);
@@ -379,7 +380,7 @@ public class SearchCoordinatorSvcImplTest {
List pids = createPidSequence(100);
SlowIterator iter = new SlowIterator(pids.iterator(), 2);
- when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter);
+ when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
@@ -462,7 +463,7 @@ public class SearchCoordinatorSvcImplTest {
params.add("name", new StringParam("ANAME"));
List pids = createPidSequence(800);
- when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(new ResultIterator(pids.iterator()));
+ when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(new ResultIterator(pids.iterator()));
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
@@ -483,7 +484,7 @@ public class SearchCoordinatorSvcImplTest {
params.add("name", new StringParam("ANAME"));
List pids = createPidSequence(800);
- when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class))).thenReturn(new ResultIterator(pids.iterator()));
+ when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class), nullable(PartitionId.class))).thenReturn(new ResultIterator(pids.iterator()));
pids = createPidSequence(110);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(eq(pids), any(Collection.class), any(List.class), anyBoolean(), nullable(RequestDetails.class));
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java
index 87f33605ee4..66f92bc150b 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java
@@ -59,7 +59,14 @@ public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersi
private Date myUpdated;
@Embedded
- private TenantId myTenantId;
+ private PartitionId myPartitionId;
+
+ /**
+ * This is here to support queries only, do not set this field directly
+ */
+ @SuppressWarnings("unused")
+ @Column(name = PartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
+ private Integer myPartitionIdValue;
/**
* This is stored as an optimization to avoid neeind to query for this
@@ -68,12 +75,12 @@ public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersi
@Transient
private transient String myTransientForcedId;
- public TenantId getTenantId() {
- return myTenantId;
+ public PartitionId getPartitionId() {
+ return myPartitionId;
}
- public void setTenantId(TenantId theTenantId) {
- myTenantId = theTenantId;
+ public void setPartitionId(PartitionId thePartitionId) {
+ myPartitionId = thePartitionId;
}
public String getTransientForcedId() {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
index 2f2d9b7024d..461432a7ac8 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
@@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
+import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
@@ -28,14 +29,21 @@ import java.io.Serializable;
public abstract class BaseResourceIndex implements Serializable {
@Embedded
- private TenantId myTenantId;
+ private PartitionId myPartitionId;
- public TenantId getTenantId() {
- return myTenantId;
+ /**
+ * This is here to support queries only, do not set this field directly
+ */
+ @SuppressWarnings("unused")
+ @Column(name = PartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
+ private Integer myPartitionIdValue;
+
+ public PartitionId getPartitionId() {
+ return myPartitionId;
}
- public void setTenantId(TenantId theTenantId) {
- myTenantId = theTenantId;
+ public void setPartitionId(PartitionId thePartitionId) {
+ myPartitionId = thePartitionId;
}
public abstract Long getId();
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java
index 8ba03fca400..aefdd8254f6 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity;
*/
import javax.persistence.Column;
+import javax.persistence.Embedded;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
@@ -38,6 +39,9 @@ public class BaseTag implements Serializable {
@Column(name = "TAG_ID", insertable = false, updatable = false)
private Long myTagId;
+ @Embedded
+ private PartitionId myPartitionId;
+
public Long getTagId() {
return myTagId;
}
@@ -50,4 +54,12 @@ public class BaseTag implements Serializable {
myTag = theTag;
}
+ public PartitionId getPartitionId() {
+ return myPartitionId;
+ }
+
+ public void setPartitionId(PartitionId thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
}
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 7807d918ecb..ced85b1ad8d 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,8 +62,9 @@ public class ForcedId {
@ColumnDefault("''")
@Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true)
private String myResourceType;
+
@Embedded
- private TenantId myTenantId;
+ private PartitionId myPartitionId;
/**
* Constructor
@@ -96,11 +97,11 @@ public class ForcedId {
return myId;
}
- public TenantId getTenantId() {
- return myTenantId;
+ public PartitionId getPartitionId() {
+ return myPartitionId;
}
- public void setTenantId(TenantId theTenantId) {
- myTenantId = theTenantId;
+ public void setPartitionId(PartitionId thePartitionId) {
+ myPartitionId = thePartitionId;
}
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionId.java
new file mode 100644
index 00000000000..a8939ecee48
--- /dev/null
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionId.java
@@ -0,0 +1,66 @@
+package ca.uhn.fhir.jpa.model.entity;
+
+import javax.annotation.Nonnull;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import java.time.LocalDate;
+
+import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
+
+@Embeddable
+public class PartitionId implements Cloneable {
+
+ static final String PARTITION_ID = "PARTITION_ID";
+
+ @Column(name = PARTITION_ID, nullable = true, insertable = true, updatable = false)
+ private Integer myPartitionId;
+ @Column(name = "PARTITION_DATE", nullable = true, insertable = true, updatable = false)
+ private LocalDate myPartitionDate;
+
+ /**
+ * Constructor
+ */
+ public PartitionId() {
+ super();
+ }
+
+ /**
+ * Constructor
+ */
+ public PartitionId(int thePartitionId, LocalDate thePartitionDate) {
+ setPartitionId(thePartitionId);
+ setPartitionDate(thePartitionDate);
+ }
+
+ @Nonnull
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+
+ public PartitionId setPartitionId(@Nonnull Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ return this;
+ }
+
+ public LocalDate getPartitionDate() {
+ return myPartitionDate;
+ }
+
+ public PartitionId setPartitionDate(LocalDate thePartitionDate) {
+ myPartitionDate = thePartitionDate;
+ return this;
+ }
+
+ @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "MethodDoesntCallSuperMethod"})
+ @Override
+ protected PartitionId clone() {
+ return new PartitionId()
+ .setPartitionId(getPartitionId())
+ .setPartitionDate(getPartitionDate());
+ }
+
+ @Override
+ public String toString() {
+ return defaultIfNull(myPartitionId, "null").toString();
+ }
+}
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 b286d68990e..ee9af4de733 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
@@ -49,7 +49,7 @@ public class ResourceHistoryProvenanceEntity {
private String myRequestId;
// FIXME: make sure this gets populated
@Embedded
- private TenantId myTenantId;
+ private PartitionId myPartitionId;
/**
* Constructor
@@ -86,11 +86,11 @@ public class ResourceHistoryProvenanceEntity {
return myId;
}
- public TenantId getTenantId() {
- return myTenantId;
+ public PartitionId getPartitionId() {
+ return myPartitionId;
}
- public void setTenantId(TenantId theTenantId) {
- myTenantId = theTenantId;
+ public void setPartitionId(PartitionId thePartitionId) {
+ myPartitionId = thePartitionId;
}
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java
index fea5496300e..08f7b2b5d53 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java
@@ -94,7 +94,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
}
public void addTag(ResourceTag theTag) {
- ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag());
+ ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag(), getPartitionId());
tag.setResourceType(theTag.getResourceType());
getTags().add(tag);
}
@@ -106,7 +106,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
return next;
}
}
- ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag);
+ ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag, getPartitionId());
getTags().add(historyTag);
return historyTag;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTag.java
index e3482c765da..bbcb9bf7834 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTag.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTag.java
@@ -75,11 +75,12 @@ public class ResourceHistoryTag extends BaseTag implements Serializable {
}
- public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag) {
+ public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag, PartitionId thePartitionId) {
setTag(theTag);
setResource(theResourceHistoryTable);
setResourceId(theResourceHistoryTable.getResourceId());
setResourceType(theResourceHistoryTable.getResourceType());
+ setPartitionId(thePartitionId);
}
public ResourceHistoryTable getResourceHistory() {
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 ee7aafa2fc2..b9c9b747ea5 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
@@ -49,7 +49,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable10.15.1.3-->
2.3.4
2.3.3
- 28.0-jre
+ 28.2-jre
2.8.5
2.2.11_1
2.3.1
From 8e714b6b14adf2387bcfd84fb43bc74f819ce796 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 30 Mar 2020 05:36:24 -0400
Subject: [PATCH 08/63] Work on uniques
---
.../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 13 +-
.../dao/predicate/BasePredicateBuilder.java | 10 ++
.../jpa/dao/predicate/PredicateBuilder.java | 32 ++---
.../dao/predicate/PredicateBuilderCoords.java | 10 +-
.../dao/predicate/PredicateBuilderDate.java | 8 +-
.../dao/predicate/PredicateBuilderNumber.java | 8 +-
.../predicate/PredicateBuilderQuantity.java | 10 +-
.../predicate/PredicateBuilderReference.java | 30 ++---
.../dao/predicate/PredicateBuilderString.java | 10 +-
.../dao/predicate/PredicateBuilderTag.java | 1 +
.../dao/predicate/PredicateBuilderToken.java | 13 +-
.../dao/predicate/PredicateBuilderUri.java | 6 +-
.../fhir/jpa/dao/r4/PartitioningR4Test.java | 111 ++++++++++++++++++
.../ResourceIndexedCompositeStringUnique.java | 6 +
14 files changed, 214 insertions(+), 54 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
index 2d68d0778a9..3599a7310d2 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
@@ -871,7 +871,7 @@ public class SearchBuilder implements ISearchBuilder {
.add(StorageProcessingMessage.class, msg);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params);
- addPredicateCompositeStringUnique(theParams, indexString);
+ addPredicateCompositeStringUnique(theParams, indexString, myPartitionId);
}
}
}
@@ -886,9 +886,16 @@ public class SearchBuilder implements ISearchBuilder {
}
}
- private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString) {
- myQueryRoot.setHasIndexJoins(true);
+ private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString, PartitionId thePartitionId) {
Join join = myQueryRoot.join("myParamsCompositeStringUnique", JoinType.LEFT);
+
+ if (thePartitionId != null) {
+ Integer partitionId = thePartitionId.getPartitionId();
+ Predicate predicate = myCriteriaBuilder.equal(join.get("myPartitionIdValue").as(Integer.class), partitionId);
+ myQueryRoot.addPredicate(predicate);
+ }
+
+ myQueryRoot.setHasIndexJoins(true);
Predicate predicate = myCriteriaBuilder.equal(join.get("myIndexString"), theIndexedString);
myQueryRoot.addPredicate(predicate);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
index ed5f8a344d0..191844780a3 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
@@ -24,9 +24,11 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
+import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
@@ -203,6 +205,14 @@ abstract class BasePredicateBuilder {
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num);
}
+ void addPartitionIdPredicate(PartitionId thePartitionId, Join theJoin, List theCodePredicates) {
+ if (thePartitionId != null) {
+ Integer partitionId = thePartitionId.getPartitionId();
+ Predicate partitionPredicate = myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue").as(Integer.class), partitionId);
+ myQueryRoot.addPredicate(partitionPredicate);
+ }
+ }
+
static String createLeftAndRightMatchLikeExpression(String likeExpression) {
return "%" + likeExpression.replace("%", "[%]") + "%";
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
index 7e88d5de53b..af31d66a1b2 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
@@ -55,40 +55,40 @@ public class PredicateBuilder {
myPredicateBuilderUri = thePredicateBuilderFactory.newPredicateBuilderUri(theSearchBuilder);
}
- void addPredicateCoords(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd) {
- myPredicateBuilderCoords.addPredicate(theResourceName, theParamName, theNextAnd, null);
+ void addPredicateCoords(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, PartitionId thePartitionId) {
+ myPredicateBuilderCoords.addPredicate(theResourceName, theParamName, theNextAnd, null, thePartitionId);
}
- Predicate addPredicateDate(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation) {
- return myPredicateBuilderDate.addPredicate(theResourceName, theParamName, theNextAnd, theOperation);
+ Predicate addPredicateDate(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
+ return myPredicateBuilderDate.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, thePartitionId);
}
- Predicate addPredicateNumber(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation) {
- return myPredicateBuilderNumber.addPredicate(theResourceName, theParamName, theNextAnd, theOperation);
+ Predicate addPredicateNumber(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
+ return myPredicateBuilderNumber.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, thePartitionId);
}
- Predicate addPredicateQuantity(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation) {
- return myPredicateBuilderQuantity.addPredicate(theResourceName, theParamName, theNextAnd, theOperation);
+ Predicate addPredicateQuantity(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
+ return myPredicateBuilderQuantity.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, thePartitionId);
}
- void addPredicateString(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd) {
- myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, SearchFilterParser.CompareOperation.sw);
+ void addPredicateString(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, PartitionId thePartitionId) {
+ myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, SearchFilterParser.CompareOperation.sw, thePartitionId);
}
- Predicate addPredicateString(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation) {
- return myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, theOperation);
+ Predicate addPredicateString(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
+ return myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, thePartitionId);
}
void addPredicateTag(List> theAndOrParams, String theParamName) {
myPredicateBuilderTag.addPredicateTag(theAndOrParams, theParamName);
}
- Predicate addPredicateToken(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation) {
- return myPredicateBuilderToken.addPredicate(theResourceName, theParamName, theNextAnd, theOperation);
+ Predicate addPredicateToken(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
+ return myPredicateBuilderToken.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, thePartitionId);
}
- Predicate addPredicateUri(String theResourceName, String theName, List extends IQueryParameterType> theSingletonList, SearchFilterParser.CompareOperation theOperation) {
- return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation);
+ Predicate addPredicateUri(String theResourceName, String theName, List extends IQueryParameterType> theSingletonList, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
+ return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation, thePartitionId);
}
public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List> theAndOrParams, RequestDetails theRequest, PartitionId thePartitionId) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java
index 933b7335e80..b00335fabe3 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
*/
import ca.uhn.fhir.jpa.dao.SearchBuilder;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.CoordCalculator;
@@ -147,15 +148,18 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
public Predicate addPredicate(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList,
- SearchFilterParser.CompareOperation theOperation) {
+ SearchFilterParser.CompareOperation theOperation,
+ PartitionId thePartitionId) {
Join join = createJoin(SearchBuilderJoinEnum.COORDS, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
+ addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
- List codePredicates = new ArrayList();
+ List codePredicates = new ArrayList<>();
+ addPartitionIdPredicate(thePartitionId, join, codePredicates);
+
for (IQueryParameterType nextOr : theList) {
Predicate singleCode = createPredicateCoords(nextOr,
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index 466182b138f..ec97488396c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
*/
import ca.uhn.fhir.jpa.dao.SearchBuilder;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
@@ -58,7 +59,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
public Predicate addPredicate(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList,
- SearchFilterParser.CompareOperation operation) {
+ SearchFilterParser.CompareOperation operation,
+ PartitionId thePartitionId) {
boolean newJoin = false;
if (myJoinMap == null) {
@@ -75,11 +77,13 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
if (theList.get(0).getMissing() != null) {
Boolean missing = theList.get(0).getMissing();
- addPredicateParamMissing(theResourceName, theParamName, missing, join);
+ addPredicateParamMissing(theResourceName, theParamName, missing, join, thePartitionId);
return null;
}
List codePredicates = new ArrayList<>();
+ addPartitionIdPredicate(thePartitionId, join, codePredicates);
+
for (IQueryParameterType nextOr : theList) {
IQueryParameterType params = nextOr;
Predicate p = createPredicateDate(params,
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java
index 21f46c0635c..e2078552568 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
*/
import ca.uhn.fhir.jpa.dao.SearchBuilder;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
@@ -53,16 +54,19 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB
public Predicate addPredicate(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList,
- SearchFilterParser.CompareOperation operation) {
+ SearchFilterParser.CompareOperation operation,
+ PartitionId thePartitionId) {
Join join = createJoin(SearchBuilderJoinEnum.NUMBER, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
+ addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
List codePredicates = new ArrayList<>();
+ addPartitionIdPredicate(thePartitionId, join, codePredicates);
+
for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof NumberParam) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java
index e9737ee56df..11747869515 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java
@@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
@@ -51,16 +52,19 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
public Predicate addPredicate(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList,
- SearchFilterParser.CompareOperation operation) {
+ SearchFilterParser.CompareOperation theOperation,
+ PartitionId thePartitionId) {
Join join = createJoin(SearchBuilderJoinEnum.QUANTITY, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
+ addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
List codePredicates = new ArrayList();
+ addPartitionIdPredicate(thePartitionId, join, codePredicates);
+
for (IQueryParameterType nextOr : theList) {
Predicate singleCode = createPredicateQuantity(nextOr,
@@ -68,7 +72,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
theParamName,
myCriteriaBuilder,
join,
- operation);
+ theOperation);
codePredicates.add(singleCode);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
index 83141850610..0050e6769ea 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
@@ -188,6 +188,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
List codePredicates = new ArrayList<>();
+ addPartitionIdPredicate(thePartitionId, join, codePredicates);
// Resources by ID
List targetPids = myIdHelperService.resolveResourcePersistentIds(targetIds, theRequest);
@@ -538,12 +539,12 @@ class PredicateBuilderReference extends BasePredicateBuilder {
switch (nextParamDef.getParamType()) {
case DATE:
for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
- myPredicateBuilder.addPredicateDate(theResourceName, theParamName, nextAnd, null);
+ myPredicateBuilder.addPredicateDate(theResourceName, theParamName, nextAnd, null, thePartitionId);
}
break;
case QUANTITY:
for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
- myPredicateBuilder.addPredicateQuantity(theResourceName, theParamName, nextAnd, null);
+ myPredicateBuilder.addPredicateQuantity(theResourceName, theParamName, nextAnd, null, thePartitionId);
}
break;
case REFERENCE:
@@ -553,21 +554,21 @@ class PredicateBuilderReference extends BasePredicateBuilder {
break;
case STRING:
for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
- myPredicateBuilder.addPredicateString(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.sw);
+ myPredicateBuilder.addPredicateString(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.sw, thePartitionId);
}
break;
case TOKEN:
for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
if ("Location.position".equals(nextParamDef.getPath())) {
- myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd);
+ myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd, thePartitionId);
} else {
- myPredicateBuilder.addPredicateToken(theResourceName, theParamName, nextAnd, null);
+ myPredicateBuilder.addPredicateToken(theResourceName, theParamName, nextAnd, null, thePartitionId);
}
}
break;
case NUMBER:
for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
- myPredicateBuilder.addPredicateNumber(theResourceName, theParamName, nextAnd, null);
+ myPredicateBuilder.addPredicateNumber(theResourceName, theParamName, nextAnd, null, thePartitionId);
}
break;
case COMPOSITE:
@@ -577,14 +578,14 @@ class PredicateBuilderReference extends BasePredicateBuilder {
break;
case URI:
for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
- myPredicateBuilder.addPredicateUri(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.eq);
+ myPredicateBuilder.addPredicateUri(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.eq, thePartitionId);
}
break;
case HAS:
case SPECIAL:
for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
if ("Location.position".equals(nextParamDef.getPath())) {
- myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd);
+ myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd, thePartitionId);
}
}
break;
@@ -688,13 +689,13 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} else {
RestSearchParameterTypeEnum typeEnum = searchParam.getParamType();
if (typeEnum == RestSearchParameterTypeEnum.URI) {
- return myPredicateBuilder.addPredicateUri(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation());
+ return myPredicateBuilder.addPredicateUri(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), thePartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.STRING) {
- return myPredicateBuilder.addPredicateString(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation());
+ return myPredicateBuilder.addPredicateString(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), thePartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.DATE) {
- return myPredicateBuilder.addPredicateDate(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new DateParam(theFilter.getValue())), theFilter.getOperation());
+ return myPredicateBuilder.addPredicateDate(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new DateParam(theFilter.getValue())), theFilter.getOperation(), thePartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) {
- return myPredicateBuilder.addPredicateNumber(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation());
+ return myPredicateBuilder.addPredicateNumber(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), thePartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) {
String paramName = theFilter.getParamPath().getName();
SearchFilterParser.CompareOperation operation = theFilter.getOperation();
@@ -704,7 +705,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value);
return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, thePartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) {
- return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation());
+ return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), thePartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
throw new InvalidRequestException("Composite search parameters not currently supported with _filter clauses");
} else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) {
@@ -713,7 +714,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
null,
null,
theFilter.getValue());
- return myPredicateBuilder.addPredicateToken(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(param), theFilter.getOperation());
+ return myPredicateBuilder.addPredicateToken(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(param), theFilter.getOperation(), thePartitionId);
}
}
return null;
@@ -754,6 +755,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
qp = new SpecialParam();
break;
}
+ throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
case URI:
case HAS:
default:
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java
index 221db21ae11..349d84e5be7 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java
@@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
@@ -56,16 +57,19 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
public Predicate addPredicate(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList,
- SearchFilterParser.CompareOperation operation) {
+ SearchFilterParser.CompareOperation theOperation,
+ PartitionId thePartitionId) {
Join join = createJoin(SearchBuilderJoinEnum.STRING, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
+ addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
List codePredicates = new ArrayList<>();
+ addPartitionIdPredicate(thePartitionId, join, codePredicates);
+
for (IQueryParameterType nextOr : theList) {
IQueryParameterType theParameter = nextOr;
Predicate singleCode = createPredicateString(theParameter,
@@ -73,7 +77,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
theParamName,
myCriteriaBuilder,
join,
- operation);
+ theOperation);
codePredicates.add(singleCode);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
index f5f4e0a4700..cb8198d9077 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
@@ -43,6 +43,7 @@ import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
+// FIXME: add partition
@Component
@Scope("prototype")
class PredicateBuilderTag extends BasePredicateBuilder {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
index 5ba6374c4ae..0a167efd735 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
@@ -26,6 +26,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@@ -70,22 +71,24 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
public Predicate addPredicate(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList,
- SearchFilterParser.CompareOperation operation) {
+ SearchFilterParser.CompareOperation theOperation,
+ PartitionId thePartitionId) {
if (theList.get(0).getMissing() != null) {
Join join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
+ addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
List codePredicates = new ArrayList<>();
+
List tokens = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof TokenParam) {
TokenParam id = (TokenParam) nextOr;
if (id.isText()) {
- myPredicateBuilder.addPredicateString(theResourceName, theParamName, theList);
+ myPredicateBuilder.addPredicateString(theResourceName, theParamName, theList, thePartitionId);
break;
}
}
@@ -98,7 +101,9 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
}
Join join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
- Collection singleCode = createPredicateToken(tokens, theResourceName, theParamName, myCriteriaBuilder, join, operation);
+ addPartitionIdPredicate(thePartitionId, join, codePredicates);
+
+ Collection singleCode = createPredicateToken(tokens, theResourceName, theParamName, myCriteriaBuilder, join, theOperation);
assert singleCode != null;
codePredicates.addAll(singleCode);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
index d4814b6617b..18de2fe86d1 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
@@ -67,10 +67,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
}
List codePredicates = new ArrayList<>();
- if (thePartitionId != null) {
- Predicate partitionPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
- codePredicates.add(partitionPredicate);
- }
+ addPartitionIdPredicate(thePartitionId, join, codePredicates);
for (IQueryParameterType nextOr : theList) {
@@ -185,4 +182,5 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
myQueryRoot.addPredicate(outerPredicate);
return outerPredicate;
}
+
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
index bf63a643bf9..be76df610b1 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
@@ -8,6 +8,8 @@ import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.DateParam;
+import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
@@ -379,6 +381,107 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
+ @Test
+ public void testSearch_StringParam_SearchAllTenants() {
+ IIdType patientIdNull = createPatient(null, withFamily("FAMILY"));
+ IIdType patientId1 = createPatient(1, withFamily("FAMILY"));
+ IIdType patientId2 = createPatient(2, withFamily("FAMILY"));
+
+ addReadTenant(null);
+
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Patient.SP_FAMILY, new StringParam("FAMILY"));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
+ }
+
+ @Test
+ public void testSearch_StringParam_SearchOneTenant() {
+ createPatient(null, withFamily("FAMILY"));
+ IIdType patientId1 = createPatient(1, withFamily("FAMILY"));
+ createPatient(2, withFamily("FAMILY"));
+
+ addReadTenant(1);
+
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Patient.SP_FAMILY, new StringParam("FAMILY"));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ myCaptureQueriesListener.logSelectQueriesForCurrentThread();
+ assertThat(ids, Matchers.contains(patientId1));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
+ }
+
+ @Test
+ public void testSearch_UniqueParam_SearchAllTenants() {
+ createUniqueCompositeSp();
+
+ IIdType id = createPatient(1, withBirthdate("2020-01-01"));
+
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01"));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ myCaptureQueriesListener.logSelectQueriesForCurrentThread();
+ assertThat(ids, Matchers.contains(id));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING='Patient?birthdate=2020-01-01'"));
+ }
+
+
+ @Test
+ public void testSearch_UniqueParam_SearchOneTenant() {
+ createUniqueCompositeSp();
+
+ IIdType id = createPatient(1, withBirthdate("2020-01-01"));
+
+ addReadTenant(1);
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01"));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ myCaptureQueriesListener.logSelectQueriesForCurrentThread();
+ assertThat(ids, Matchers.contains(id));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING='Patient?birthdate=2020-01-01'"));
+
+ // Same query, different partition
+ addReadTenant(2);
+ myCaptureQueriesListener.clear();
+ map = new SearchParameterMap();
+ map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01"));
+ map.setLoadSynchronous(true);
+ results = myPatientDao.search(map);
+ ids = toUnqualifiedVersionlessIds(results);
+ myCaptureQueriesListener.logSelectQueriesForCurrentThread();
+ assertThat(ids, Matchers.empty());
+
+ }
+
private void createUniqueCompositeSp() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate");
@@ -447,6 +550,14 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
return t -> t.setActive(true);
}
+ private Consumer withFamily(String theFamily) {
+ return t -> t.addName().setFamily(theFamily);
+ }
+
+ private Consumer withBirthdate(String theBirthdate) {
+ return t -> t.getBirthDateElement().setValueAsString(theBirthdate);
+ }
+
private Consumer withId(String theId) {
return t -> {
assertThat(theId, matchesPattern("[a-zA-Z0-9]+"));
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 b9c9b747ea5..86db8be7253 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
@@ -50,6 +50,12 @@ public class ResourceIndexedCompositeStringUnique implements Comparable
Date: Mon, 30 Mar 2020 09:49:17 -0400
Subject: [PATCH 09/63] Work on multitenancy
---
.../dao/predicate/BasePredicateBuilder.java | 3 +-
.../jpa/dao/predicate/PredicateBuilder.java | 4 +-
.../predicate/PredicateBuilderReference.java | 2 +-
.../dao/predicate/PredicateBuilderTag.java | 23 +-
.../fhir/jpa/dao/r4/PartitioningR4Test.java | 201 +++++++++++-------
.../jpa/model/entity/BasePartitionable.java | 30 +++
.../jpa/model/entity/BaseResourceIndex.java | 22 +-
.../ca/uhn/fhir/jpa/model/entity/BaseTag.java | 13 +-
8 files changed, 183 insertions(+), 115 deletions(-)
create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
index 191844780a3..ba2079b50c6 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
@@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
+import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
@@ -205,7 +206,7 @@ abstract class BasePredicateBuilder {
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num);
}
- void addPartitionIdPredicate(PartitionId thePartitionId, Join theJoin, List theCodePredicates) {
+ void addPartitionIdPredicate(PartitionId thePartitionId, Join theJoin, List theCodePredicates) {
if (thePartitionId != null) {
Integer partitionId = thePartitionId.getPartitionId();
Predicate partitionPredicate = myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue").as(Integer.class), partitionId);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
index af31d66a1b2..512017dac1a 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java
@@ -79,8 +79,8 @@ public class PredicateBuilder {
return myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, thePartitionId);
}
- void addPredicateTag(List> theAndOrParams, String theParamName) {
- myPredicateBuilderTag.addPredicateTag(theAndOrParams, theParamName);
+ void addPredicateTag(List> theAndOrParams, String theParamName, PartitionId thePartitionId) {
+ myPredicateBuilderTag.addPredicateTag(theAndOrParams, theParamName, thePartitionId);
}
Predicate addPredicateToken(String theResourceName, String theParamName, List extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
index 0050e6769ea..73708fabbaf 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
@@ -525,7 +525,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
case Constants.PARAM_TAG:
case Constants.PARAM_PROFILE:
case Constants.PARAM_SECURITY:
- myPredicateBuilder.addPredicateTag(theAndOrParams, theParamName);
+ myPredicateBuilder.addPredicateTag(theAndOrParams, theParamName, thePartitionId);
break;
case Constants.PARAM_SOURCE:
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
index cb8198d9077..b741702321c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
*/
import ca.uhn.fhir.jpa.dao.SearchBuilder;
+import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
@@ -38,12 +39,18 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
-import javax.persistence.criteria.*;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.From;
+import javax.persistence.criteria.Join;
+import javax.persistence.criteria.JoinType;
+import javax.persistence.criteria.Path;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import javax.persistence.criteria.Subquery;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
-// FIXME: add partition
@Component
@Scope("prototype")
class PredicateBuilderTag extends BasePredicateBuilder {
@@ -53,7 +60,7 @@ class PredicateBuilderTag extends BasePredicateBuilder {
super(theSearchBuilder);
}
- void addPredicateTag(List> theList, String theParamName) {
+ void addPredicateTag(List> theList, String theParamName, PartitionId thePartitionId) {
TagTypeEnum tagType;
if (Constants.PARAM_TAG.equals(theParamName)) {
tagType = TagTypeEnum.TAG;
@@ -127,6 +134,8 @@ class PredicateBuilderTag extends BasePredicateBuilder {
continue;
}
+ // FIXME: add test for tag:not
+ // FIXME: add test for :missing
if (paramInverted) {
ourLog.debug("Searching for _tag:not");
@@ -158,7 +167,13 @@ class PredicateBuilderTag extends BasePredicateBuilder {
From defJoin = tagJoin.join("myTag");
Predicate tagListPredicate = createPredicateTagList(defJoin, myCriteriaBuilder, tagType, tokens);
- myQueryRoot.addPredicate(tagListPredicate);
+ List predicates = Lists.newArrayList(tagListPredicate);
+
+ if (thePartitionId != null) {
+ addPartitionIdPredicate(thePartitionId, tagJoin, predicates);
+ }
+
+ myQueryRoot.addPredicates(predicates);
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
index be76df610b1..f8c45582de5 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
@@ -7,9 +7,11 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
@@ -50,13 +52,13 @@ import static org.mockito.Mockito.when;
public class PartitioningR4Test extends BaseJpaR4SystemTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PartitioningR4Test.class);
- private MyInterceptor myTenantInterceptor;
- private LocalDate myTenantDate;
+ private MyInterceptor myPartitionInterceptor;
+ private LocalDate myPartitionDate;
private int myPartitionId;
@After
public void after() {
- myTenantInterceptor.assertNoRemainingIds();
+ myPartitionInterceptor.assertNoRemainingIds();
myDaoConfig.setPartitioningEnabled(new DaoConfig().isPartitioningEnabled());
@@ -73,15 +75,15 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
myDaoConfig.setUniqueIndexesEnabled(true);
myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
- myTenantDate = LocalDate.of(2020, Month.JANUARY, 14);
+ myPartitionDate = LocalDate.of(2020, Month.JANUARY, 14);
myPartitionId = 3;
- myTenantInterceptor = new MyInterceptor();
+ myPartitionInterceptor = new MyInterceptor();
}
@Test
- public void testCreateResourceNoTenant() {
+ public void testCreateResourceNoPartition() {
Patient p = new Patient();
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
@@ -95,12 +97,12 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
@Test
- public void testCreateResourceWithTenant() {
+ public void testCreateResourceWithPartition() {
createUniqueCompositeSp();
createRequestId();
- addCreateTenant(myPartitionId, myTenantDate);
- addCreateTenant(myPartitionId, myTenantDate);
+ addCreatePartition(myPartitionId, myPartitionDate);
+ addCreatePartition(myPartitionId, myPartitionDate);
Organization org = new Organization();
org.setName("org");
@@ -118,71 +120,71 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate());
// HFJ_RES_TAG
List tags = myResourceTagDao.findAll();
assertEquals(1, tags.size());
assertEquals(myPartitionId, tags.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, tags.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, tags.get(0).getPartitionId().getPartitionDate());
// HFJ_RES_VER
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
assertEquals(myPartitionId, version.getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, version.getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, version.getPartitionId().getPartitionDate());
// HFJ_HISTORY_TAG
List historyTags = myResourceHistoryTagDao.findAll();
assertEquals(1, historyTags.size());
assertEquals(myPartitionId, historyTags.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, historyTags.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, historyTags.get(0).getPartitionId().getPartitionDate());
// HFJ_RES_VER_PROV
assertNotNull(version.getProvenance());
assertEquals(myPartitionId, version.getProvenance().getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, version.getProvenance().getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, version.getProvenance().getPartitionId().getPartitionDate());
// 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(myPartitionId, strings.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, strings.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, strings.get(0).getPartitionId().getPartitionDate());
// 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(myPartitionId, dates.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, dates.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, dates.get(0).getPartitionId().getPartitionDate());
assertEquals(myPartitionId, dates.get(1).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, dates.get(1).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, dates.get(1).getPartitionId().getPartitionDate());
// HFJ_RES_LINK
List resourceLinks = myResourceLinkDao.findAllForResourceId(patientId);
assertEquals(1, resourceLinks.size());
assertEquals(myPartitionId, resourceLinks.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, resourceLinks.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, resourceLinks.get(0).getPartitionId().getPartitionDate());
// HFJ_RES_PARAM_PRESENT
List presents = mySearchParamPresentDao.findAllForResource(resourceTable);
assertEquals(myPartitionId, presents.size());
assertEquals(myPartitionId, presents.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, presents.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, presents.get(0).getPartitionId().getPartitionDate());
// HFJ_IDX_CMP_STRING_UNIQ
List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId);
assertEquals(1, uniques.size());
assertEquals(myPartitionId, uniques.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, uniques.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, uniques.get(0).getPartitionId().getPartitionDate());
});
}
@Test
public void testCreateWithForcedId() {
- addCreateTenant(myPartitionId, myTenantDate);
- addCreateTenant(myPartitionId, myTenantDate);
+ addCreatePartition(myPartitionId, myPartitionDate);
+ addCreatePartition(myPartitionId, myPartitionDate);
Organization org = new Organization();
org.setId("org");
@@ -199,18 +201,18 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
List forcedIds = myForcedIdDao.findAll();
assertEquals(2, forcedIds.size());
assertEquals(myPartitionId, forcedIds.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, forcedIds.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, forcedIds.get(0).getPartitionId().getPartitionDate());
assertEquals(myPartitionId, forcedIds.get(1).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, forcedIds.get(1).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, forcedIds.get(1).getPartitionId().getPartitionDate());
});
}
@Test
- public void testUpdateResourceWithTenant() {
+ public void testUpdateResourceWithPartition() {
createRequestId();
- addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
- addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
+ addCreatePartition(3, LocalDate.of(2020, Month.JANUARY, 14));
+ addCreatePartition(3, LocalDate.of(2020, Month.JANUARY, 14));
// Create a resource
Patient p = new Patient();
@@ -221,7 +223,7 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate());
});
// Update that resource
@@ -234,66 +236,66 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate());
// HFJ_RES_VER
int version = 2;
ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, version);
assertEquals(myPartitionId, resVer.getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, resVer.getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, resVer.getPartitionId().getPartitionDate());
// HFJ_HISTORY_TAG
List historyTags = myResourceHistoryTagDao.findAll();
assertEquals(2, historyTags.size());
assertEquals(myPartitionId, historyTags.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, historyTags.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, historyTags.get(0).getPartitionId().getPartitionDate());
assertEquals(myPartitionId, historyTags.get(1).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, historyTags.get(1).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, historyTags.get(1).getPartitionId().getPartitionDate());
// HFJ_RES_VER_PROV
assertNotNull(resVer.getProvenance());
assertNotNull(resVer.getPartitionId());
assertEquals(myPartitionId, resVer.getProvenance().getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, resVer.getProvenance().getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, resVer.getProvenance().getPartitionId().getPartitionDate());
// 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(myPartitionId, strings.get(0).getPartitionId().getPartitionId().intValue());
- assertEquals(myTenantDate, strings.get(0).getPartitionId().getPartitionDate());
+ assertEquals(myPartitionDate, strings.get(0).getPartitionId().getPartitionDate());
});
}
@Test
- public void testReadAcrossTenants() {
+ public void testReadAcrossPartitions() {
IIdType patientId1 = createPatient(1, withActiveTrue());
IIdType patientId2 = createPatient(2, withActiveTrue());
- addReadTenant(null);
+ addReadPartition(null);
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
- addReadTenant(null);
+ addReadPartition(null);
IdType gotId2 = myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId2, gotId2);
}
@Test
- public void testReadSpecificTenant_PidId() {
+ public void testReadSpecificPartition_PidId() {
IIdType patientIdNull = createPatient(null, withActiveTrue());
IIdType patientId1 = createPatient(1, withActiveTrue());
IIdType patientId2 = createPatient(2, withActiveTrue());
- // Read in correct tenant
- addReadTenant(1);
+ // Read in correct Partition
+ addReadPartition(1);
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
- // Read in null tenant
- addReadTenant(1);
+ // Read in null Partition
+ addReadPartition(1);
try {
myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless();
fail();
@@ -301,8 +303,8 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
}
- // Read in wrong tenant
- addReadTenant(1);
+ // Read in wrong Partition
+ addReadPartition(1);
try {
myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
fail();
@@ -312,18 +314,18 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
- public void testReadSpecificTenant_ForcedId() {
+ public void testReadSpecificPartition_ForcedId() {
IIdType patientIdNull = createPatient(null, withActiveTrue(), withId("NULL"));
IIdType patientId1 = createPatient(1, withActiveTrue(), withId("ONE"));
IIdType patientId2 = createPatient(2, withActiveTrue(), withId("TWO"));
- // Read in correct tenant
- addReadTenant(1);
+ // Read in correct Partition
+ addReadPartition(1);
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
- // Read in null tenant
- addReadTenant(1);
+ // Read in null Partition
+ addReadPartition(1);
try {
myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless();
fail();
@@ -331,8 +333,8 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
}
- // Read in wrong tenant
- addReadTenant(1);
+ // Read in wrong Partition
+ addReadPartition(1);
try {
myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
fail();
@@ -342,12 +344,12 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
- public void testSearch_NoParams_SearchAllTenants() {
+ public void testSearch_NoParams_SearchAllPartitions() {
IIdType patientIdNull = createPatient(null, withActiveTrue());
IIdType patientId1 = createPatient(1, withActiveTrue());
IIdType patientId2 = createPatient(2, withActiveTrue());
- addReadTenant(null);
+ addReadPartition(null);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
@@ -362,12 +364,12 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
- public void testSearch_NoParams_SearchOneTenant() {
+ public void testSearch_NoParams_SearchOnePartition() {
createPatient(null, withActiveTrue());
IIdType patientId1 = createPatient(1, withActiveTrue());
createPatient(2, withActiveTrue());
- addReadTenant(1);
+ addReadPartition(1);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
@@ -382,12 +384,12 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
- public void testSearch_StringParam_SearchAllTenants() {
+ public void testSearch_StringParam_SearchAllPartitions() {
IIdType patientIdNull = createPatient(null, withFamily("FAMILY"));
IIdType patientId1 = createPatient(1, withFamily("FAMILY"));
IIdType patientId2 = createPatient(2, withFamily("FAMILY"));
- addReadTenant(null);
+ addReadPartition(null);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
@@ -404,12 +406,12 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
- public void testSearch_StringParam_SearchOneTenant() {
+ public void testSearch_StringParam_SearchOnePartition() {
createPatient(null, withFamily("FAMILY"));
IIdType patientId1 = createPatient(1, withFamily("FAMILY"));
createPatient(2, withFamily("FAMILY"));
- addReadTenant(1);
+ addReadPartition(1);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
@@ -427,11 +429,57 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
- public void testSearch_UniqueParam_SearchAllTenants() {
+ public void testSearch_TagParam_SearchAllPartitions() {
+ IIdType patientIdNull = createPatient(null, withActiveTrue(), withTag("http://system", "code"));
+ IIdType patientId1 = createPatient(1, withActiveTrue(), withTag("http://system", "code"));
+ IIdType patientId2 = createPatient(2, withActiveTrue(), withTag("http://system", "code"));
+
+ addReadPartition(null);
+
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code"));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'"));
+ }
+
+ @Test
+ public void testSearch_TagParam_SearchOnePartition() {
+ IIdType patientIdNull = createPatient(null, withActiveTrue(), withTag("http://system", "code"));
+ IIdType patientId1 = createPatient(1, withActiveTrue(), withTag("http://system", "code"));
+ IIdType patientId2 = createPatient(2, withActiveTrue(), withTag("http://system", "code"));
+
+ addReadPartition(1);
+
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code"));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ assertThat(ids, Matchers.contains(patientId1));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'"));
+ }
+
+ @Test
+ public void testSearch_UniqueParam_SearchAllPartitions() {
createUniqueCompositeSp();
IIdType id = createPatient(1, withBirthdate("2020-01-01"));
+ addReadPartition(null);
+
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01"));
@@ -449,12 +497,12 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
@Test
- public void testSearch_UniqueParam_SearchOneTenant() {
+ public void testSearch_UniqueParam_SearchOnePartition() {
createUniqueCompositeSp();
IIdType id = createPatient(1, withBirthdate("2020-01-01"));
- addReadTenant(1);
+ addReadPartition(1);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01"));
@@ -470,7 +518,7 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING='Patient?birthdate=2020-01-01'"));
// Same query, different partition
- addReadTenant(2);
+ addReadPartition(2);
myCaptureQueriesListener.clear();
map = new SearchParameterMap();
map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01"));
@@ -509,29 +557,29 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
- private void addCreateTenant(int thePartitionId, LocalDate theTenantDate) {
+ private void addCreatePartition(int thePartitionId, LocalDate thePartitionDate) {
registerInterceptorIfNeeded();
- myTenantInterceptor.addCreateTenant(new PartitionId(thePartitionId, theTenantDate));
+ myPartitionInterceptor.addCreatePartition(new PartitionId(thePartitionId, thePartitionDate));
}
- private void addReadTenant(Integer thePartitionId) {
+ private void addReadPartition(Integer thePartitionId) {
registerInterceptorIfNeeded();
PartitionId partitionId = null;
if (thePartitionId != null) {
partitionId = new PartitionId(thePartitionId, null);
}
- myTenantInterceptor.addReadTenant(partitionId);
+ myPartitionInterceptor.addReadPartition(partitionId);
}
private void registerInterceptorIfNeeded() {
- if (!myInterceptorRegistry.getAllRegisteredInterceptors().contains(myTenantInterceptor)) {
- myInterceptorRegistry.registerInterceptor(myTenantInterceptor);
+ if (!myInterceptorRegistry.getAllRegisteredInterceptors().contains(myPartitionInterceptor)) {
+ myInterceptorRegistry.registerInterceptor(myPartitionInterceptor);
}
}
public IIdType createPatient(Integer thePartitionId, Consumer... theModifiers) {
if (thePartitionId != null) {
- addCreateTenant(thePartitionId, null);
+ addCreatePartition(thePartitionId, null);
}
Patient p = new Patient();
@@ -565,6 +613,11 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
};
}
+ private Consumer withTag(String theSystem, String theCode) {
+ return t->t.getMeta().addTag(theSystem, theCode, theCode);
+ }
+
+
@Interceptor
public static class MyInterceptor {
@@ -572,17 +625,17 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
private final List myCreatePartitionIds = new ArrayList<>();
private final List myReadPartitionIds = new ArrayList<>();
- public void addCreateTenant(PartitionId thePartitionId) {
+ public void addCreatePartition(PartitionId thePartitionId) {
Validate.notNull(thePartitionId);
myCreatePartitionIds.add(thePartitionId);
}
- public void addReadTenant(PartitionId thePartitionId) {
+ public void addReadPartition(PartitionId thePartitionId) {
myReadPartitionIds.add(thePartitionId);
}
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE)
- public PartitionId tenantIdentifyCreate(IBaseResource theResource, ServletRequestDetails theRequestDetails) {
+ public PartitionId PartitionIdentifyCreate(IBaseResource theResource, ServletRequestDetails theRequestDetails) {
assertNotNull(theResource);
PartitionId retVal = myCreatePartitionIds.remove(0);
ourLog.info("Returning partition for create: {}", retVal);
@@ -590,7 +643,7 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
- public PartitionId tenantIdentifyRead(ServletRequestDetails theRequestDetails) {
+ public PartitionId PartitionIdentifyRead(ServletRequestDetails theRequestDetails) {
PartitionId retVal = myReadPartitionIds.remove(0);
ourLog.info("Returning partition for read: {}", retVal);
return retVal;
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java
new file mode 100644
index 00000000000..b54b55579e6
--- /dev/null
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java
@@ -0,0 +1,30 @@
+package ca.uhn.fhir.jpa.model.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.MappedSuperclass;
+import java.io.Serializable;
+
+@MappedSuperclass
+public class BasePartitionable implements Serializable {
+
+ @Embedded
+ private PartitionId myPartitionId;
+
+ /**
+ * This is here to support queries only, do not set this field directly
+ */
+ @SuppressWarnings("unused")
+ @Column(name = PartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
+ private Integer myPartitionIdValue;
+
+ public PartitionId getPartitionId() {
+ return myPartitionId;
+ }
+
+ public void setPartitionId(PartitionId thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
+
+}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
index 461432a7ac8..63ccd1d610b 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java
@@ -20,31 +20,11 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
-import javax.persistence.Column;
-import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
@MappedSuperclass
-public abstract class BaseResourceIndex implements Serializable {
-
- @Embedded
- private PartitionId myPartitionId;
-
- /**
- * This is here to support queries only, do not set this field directly
- */
- @SuppressWarnings("unused")
- @Column(name = PartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
- private Integer myPartitionIdValue;
-
- public PartitionId getPartitionId() {
- return myPartitionId;
- }
-
- public void setPartitionId(PartitionId thePartitionId) {
- myPartitionId = thePartitionId;
- }
+public abstract class BaseResourceIndex extends BasePartitionable implements Serializable {
public abstract Long getId();
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java
index aefdd8254f6..43f9bfbaee4 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java
@@ -28,7 +28,7 @@ import javax.persistence.MappedSuperclass;
import java.io.Serializable;
@MappedSuperclass
-public class BaseTag implements Serializable {
+public class BaseTag extends BasePartitionable implements Serializable {
private static final long serialVersionUID = 1L;
@@ -39,9 +39,6 @@ public class BaseTag implements Serializable {
@Column(name = "TAG_ID", insertable = false, updatable = false)
private Long myTagId;
- @Embedded
- private PartitionId myPartitionId;
-
public Long getTagId() {
return myTagId;
}
@@ -54,12 +51,4 @@ public class BaseTag implements Serializable {
myTag = theTag;
}
- public PartitionId getPartitionId() {
- return myPartitionId;
- }
-
- public void setPartitionId(PartitionId thePartitionId) {
- myPartitionId = thePartitionId;
- }
-
}
From ea1f35beaac46d2c00537e6f8471b2f90819e9d9 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 31 Mar 2020 10:33:47 -0400
Subject: [PATCH 10/63] Work on multitenancy
---
.../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 5 +-
.../RequestPartitionHelperService.java | 8 +
.../dao/predicate/BasePredicateBuilder.java | 26 +--
.../dao/predicate/PredicateBuilderCoords.java | 3 +-
.../dao/predicate/PredicateBuilderDate.java | 3 +-
.../dao/predicate/PredicateBuilderNumber.java | 3 +-
.../predicate/PredicateBuilderQuantity.java | 3 +-
.../predicate/PredicateBuilderReference.java | 3 +-
.../dao/predicate/PredicateBuilderString.java | 5 +-
.../dao/predicate/PredicateBuilderTag.java | 2 +-
.../dao/predicate/PredicateBuilderToken.java | 5 +-
.../dao/predicate/PredicateBuilderUri.java | 4 +-
.../uhn/fhir/jpa/dao/predicate/QueryRoot.java | 2 +-
.../fhir/jpa/dao/r4/PartitioningR4Test.java | 218 ++++++++++++++++--
.../jpa/model/entity/SearchParamPresent.java | 14 +-
15 files changed, 247 insertions(+), 57 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
index 3599a7310d2..d26608c71f6 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
@@ -351,8 +351,7 @@ public class SearchBuilder implements ISearchBuilder {
* If we have any joins to index tables, we get this behaviour already guaranteed so we don't
* need an explicit predicate for it.
*/
- boolean haveNoIndexSearchParams = myParams.size() == 0 || myParams.keySet().stream().allMatch(t -> t.startsWith("_"));
- if (haveNoIndexSearchParams) {
+ if (!myQueryRoot.hasIndexJoins()) {
if (myParams.getEverythingMode() == null) {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
}
@@ -895,7 +894,7 @@ public class SearchBuilder implements ISearchBuilder {
myQueryRoot.addPredicate(predicate);
}
- myQueryRoot.setHasIndexJoins(true);
+ myQueryRoot.setHasIndexJoins();
Predicate predicate = myCriteriaBuilder.equal(join.get("myIndexString"), theIndexedString);
myQueryRoot.addPredicate(predicate);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/partition/RequestPartitionHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/partition/RequestPartitionHelperService.java
index b74f438ebf5..fbdfa51cc6b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/partition/RequestPartitionHelperService.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/partition/RequestPartitionHelperService.java
@@ -53,6 +53,10 @@ public class RequestPartitionHelperService {
*/
@Nullable
public PartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType) {
+ if (myPartitioningBlacklist.contains(theResourceType)) {
+ return null;
+ }
+
PartitionId partitionId = null;
if (myDaoConfig.isPartitioningEnabled()) {
@@ -73,6 +77,10 @@ public class RequestPartitionHelperService {
*/
@Nullable
public PartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource) {
+ String resourceType = myFhirContext.getResourceDefinition(theResource).getName();
+ if (myPartitioningBlacklist.contains(resourceType)) {
+ return null;
+ }
PartitionId partitionId = null;
if (myDaoConfig.isPartitioningEnabled()) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
index ba2079b50c6..c3b0d75b933 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
@@ -25,11 +25,9 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
-import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
-import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
@@ -45,6 +43,7 @@ import javax.annotation.PostConstruct;
import javax.persistence.criteria.*;
import java.math.BigDecimal;
import java.math.MathContext;
+import java.util.ArrayList;
import java.util.List;
abstract class BasePredicateBuilder {
@@ -112,30 +111,29 @@ abstract class BasePredicateBuilder {
return (Join) join;
}
- void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing) {
-// if (myDontUseHashesForSearch) {
-// Join paramPresentJoin = myQueryRoot.join("mySearchParamPresents", JoinType.LEFT);
-// Join paramJoin = paramPresentJoin.join("mySearchParam", JoinType.LEFT);
-//
-// myQueryRoot.addPredicate(myBuilder.equal(paramJoin.get("myResourceName"), theResourceName));
-// myQueryRoot.addPredicate(myBuilder.equal(paramJoin.get("myParamName"), theParamName));
-// myQueryRoot.addPredicate(myBuilder.equal(paramPresentJoin.get("myPresent"), !theMissing));
-// }
-
+ void addPredicateParamMissingForReference(String theResourceName, String theParamName, boolean theMissing, PartitionId thePartitionId) {
Join paramPresentJoin = myQueryRoot.join("mySearchParamPresents", JoinType.LEFT);
Expression hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
Long hash = SearchParamPresent.calculateHashPresence(theResourceName, theParamName, !theMissing);
- myQueryRoot.addPredicate(myCriteriaBuilder.equal(hashPresence, hash));
+
+ List predicates = new ArrayList<>();
+ predicates.add(myCriteriaBuilder.equal(hashPresence, hash));
+
+ addPartitionIdPredicate(thePartitionId, paramPresentJoin, predicates);
+
+ myQueryRoot.setHasIndexJoins();
+ myQueryRoot.addPredicates(predicates);
}
- void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join theJoin, PartitionId thePartitionId) {
+ void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, Join theJoin, PartitionId thePartitionId) {
if (thePartitionId != null) {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), thePartitionId.getPartitionId()));
}
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
+ myQueryRoot.setHasIndexJoins();
}
Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java
index b00335fabe3..50e575ebba8 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java
@@ -153,7 +153,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
Join join = createJoin(SearchBuilderJoinEnum.COORDS, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
+ addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
@@ -173,6 +173,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(retVal);
+ myQueryRoot.setHasIndexJoins();
return retVal;
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index ec97488396c..34bd8e71503 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -77,7 +77,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
if (theList.get(0).getMissing() != null) {
Boolean missing = theList.get(0).getMissing();
- addPredicateParamMissing(theResourceName, theParamName, missing, join, thePartitionId);
+ addPredicateParamMissingForNonReference(theResourceName, theParamName, missing, join, thePartitionId);
return null;
}
@@ -97,6 +97,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
Predicate orPredicates = myCriteriaBuilder.or(toArray(codePredicates));
+ myQueryRoot.setHasIndexJoins();
if (newJoin) {
Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, orPredicates);
myQueryRoot.addPredicate(identityAndValuePredicate);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java
index e2078552568..736243dea9f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java
@@ -60,7 +60,7 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB
Join join = createJoin(SearchBuilderJoinEnum.NUMBER, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
+ addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
@@ -109,6 +109,7 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB
}
Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates));
+ myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(predicate);
return predicate;
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java
index 11747869515..b868dba4349 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java
@@ -58,7 +58,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
Join join = createJoin(SearchBuilderJoinEnum.QUANTITY, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
+ addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
@@ -77,6 +77,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
}
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
+ myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(retVal);
return retVal;
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
index 73708fabbaf..d641885a7c0 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
@@ -137,7 +137,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing());
+ addPredicateParamMissingForReference(theResourceName, theParamName, theList.get(0).getMissing(), thePartitionId);
return null;
}
@@ -227,6 +227,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
codePredicates.add(myCriteriaBuilder.and(pathPredicate, pidPredicate));
}
+ myQueryRoot.setHasIndexJoins();
if (codePredicates.size() > 0) {
Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(predicate);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java
index 349d84e5be7..4c49ada29a5 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java
@@ -63,7 +63,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
Join join = createJoin(SearchBuilderJoinEnum.STRING, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
+ addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
@@ -82,7 +82,10 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
}
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
+
+ myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(retVal);
+
return retVal;
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
index b741702321c..e1e7e324312 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
@@ -134,7 +134,6 @@ class PredicateBuilderTag extends BasePredicateBuilder {
continue;
}
- // FIXME: add test for tag:not
// FIXME: add test for :missing
if (paramInverted) {
ourLog.debug("Searching for _tag:not");
@@ -173,6 +172,7 @@ class PredicateBuilderTag extends BasePredicateBuilder {
addPartitionIdPredicate(thePartitionId, tagJoin, predicates);
}
+ myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicates(predicates);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
index 0a167efd735..3d22b256e0c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
@@ -76,7 +76,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
if (theList.get(0).getMissing() != null) {
Join join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
+ addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
@@ -108,7 +108,10 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
codePredicates.addAll(singleCode);
Predicate spPredicate = myCriteriaBuilder.or(toArray(codePredicates));
+
+ myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(spPredicate);
+
return spPredicate;
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
index 18de2fe86d1..586b77d7998 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java
@@ -62,7 +62,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
Join join = createJoin(SearchBuilderJoinEnum.URI, theParamName);
if (theList.get(0).getMissing() != null) {
- addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
+ addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null;
}
@@ -169,6 +169,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
*/
if (codePredicates.isEmpty()) {
Predicate predicate = myCriteriaBuilder.isNull(join.get("myMissing").as(String.class));
+ myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(predicate);
return null;
}
@@ -179,6 +180,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
theParamName,
join,
orPredicate);
+ myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(outerPredicate);
return outerPredicate;
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java
index 3eeeed71efe..320a763ed59 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java
@@ -96,7 +96,7 @@ public class QueryRoot {
return myHasIndexJoins;
}
- public void setHasIndexJoins(boolean theHasIndexJoins) {
+ public void setHasIndexJoins() {
myHasIndexJoins = true;
}
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
index f8c45582de5..8cdab1619a5 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
@@ -12,11 +12,11 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Validate;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@@ -79,11 +79,14 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
myPartitionId = 3;
myPartitionInterceptor = new MyInterceptor();
+ myInterceptorRegistry.registerInterceptor(myPartitionInterceptor);
}
@Test
public void testCreateResourceNoPartition() {
+ addCreatePartition(null, null);
+
Patient p = new Patient();
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
@@ -343,6 +346,142 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
}
+ @Test
+ public void testSearch_MissingParamString_SearchAllPartitions() {
+ IIdType patientIdNull = createPatient(null, withFamily("FAMILY"));
+ IIdType patientId1 = createPatient(1, withFamily("FAMILY"));
+ IIdType patientId2 = createPatient(2, withFamily("FAMILY"));
+
+ // :missing=true
+ {
+ addReadPartition(null);
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Patient.SP_ACTIVE, new StringParam().setMissing(true));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='true'"));
+ }
+
+ // :missing=false
+ {
+ addReadPartition(null);
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Patient.SP_FAMILY, new StringParam().setMissing(false));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='false'"));
+ }
+ }
+
+ @Test
+ public void testSearch_MissingParamReference_SearchAllPartitions() {
+ IIdType patientIdNull = createPatient(null, withFamily("FAMILY"));
+ IIdType patientId1 = createPatient(1, withFamily("FAMILY"));
+ IIdType patientId2 = createPatient(2, withFamily("FAMILY"));
+
+ // :missing=true
+ {
+ addReadPartition(null);
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Patient.SP_GENERAL_PRACTITIONER, new StringParam().setMissing(true));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List ids = toUnqualifiedVersionlessIds(results);
+ assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
+
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
+ ourLog.info("Search SQL:\n{}", searchSql);
+ assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"));
+ assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='1919227773735728687'"));
+ }
+ }
+
+
+ @Test
+ public void testSearch_MissingParamString_SearchOnePartition() {
+ createPatient(null, withFamily("FAMILY"));
+ IIdType patientId1 = createPatient(1, withFamily("FAMILY"));
+ createPatient(2, withFamily("FAMILY"));
+
+ // :missing=true
+ {
+ addReadPartition(1);
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Patient.SP_ACTIVE, new StringParam().setMissing(true));
+ map.setLoadSynchronous(true);
+ IBundleProvider results = myPatientDao.search(map);
+ List