Work on multitenancy
This commit is contained in:
parent
b2d2346228
commit
f7ec41ffc5
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.interceptor.api;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public interface IInterceptorService extends IInterceptorBroadcaster {
|
public interface IInterceptorService extends IInterceptorBroadcaster {
|
||||||
|
|
||||||
|
@ -90,4 +91,8 @@ public interface IInterceptorService extends IInterceptorBroadcaster {
|
||||||
|
|
||||||
void registerInterceptors(@Nullable Collection<?> theInterceptors);
|
void registerInterceptors(@Nullable Collection<?> theInterceptors);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters all interceptors that are indicated by the given callback function returning <code>true</code>
|
||||||
|
*/
|
||||||
|
void unregisterInterceptorsIf(Function<Object, Boolean> theShouldUnregisterFunction);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1321,6 +1321,43 @@ public enum Pointcut {
|
||||||
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
||||||
),
|
),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>Storage Hook:</b>
|
||||||
|
* Invoked before an <code>$expunge</code> operation on all data (expungeEverything) is called.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
* Hooks may accept the following parameters:
|
||||||
|
* <ul>
|
||||||
|
* org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned.
|
||||||
|
* <li>
|
||||||
|
* 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.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* 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.
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Hooks should return an instance of <code>ca.uhn.fhir.jpa.model.entity.TenantId</code> or <code>null</code>.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
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"
|
||||||
|
),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>Performance Tracing Hook:</b>
|
* <b>Performance Tracing Hook:</b>
|
||||||
* This hook is invoked when any informational messages generated by the
|
* This hook is invoked when any informational messages generated by the
|
||||||
|
|
|
@ -41,6 +41,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class InterceptorService implements IInterceptorService, IInterceptorBroadcaster {
|
public class InterceptorService implements IInterceptorService, IInterceptorBroadcaster {
|
||||||
|
@ -145,6 +146,22 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterInterceptorsIf(Function<Object, Boolean> theShouldUnregisterFunction) {
|
||||||
|
unregisterInterceptorsIf(theShouldUnregisterFunction, myGlobalInvokers);
|
||||||
|
unregisterInterceptorsIf(theShouldUnregisterFunction, myAnonymousInvokers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterInterceptorsIf(Function<Object, Boolean> theShouldUnregisterFunction, ListMultimap<Pointcut, BaseInvoker> theGlobalInvokers) {
|
||||||
|
for (Iterator<Map.Entry<Pointcut, BaseInvoker>> iter = theGlobalInvokers.entries().iterator(); iter.hasNext(); ) {
|
||||||
|
Map.Entry<Pointcut, BaseInvoker> next = iter.next();
|
||||||
|
Object nextInterceptor = next.getValue().getInterceptor();
|
||||||
|
if (theShouldUnregisterFunction.apply(nextInterceptor)) {
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean registerThreadLocalInterceptor(Object theInterceptor) {
|
public boolean registerThreadLocalInterceptor(Object theInterceptor) {
|
||||||
if (!myThreadlocalInvokersEnabled) {
|
if (!myThreadlocalInvokersEnabled) {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
title: "The version of a few dependencies have been bumped to the latest versions
|
title: "The version of a few dependencies have been bumped to the latest versions
|
||||||
(dependent HAPI modules listed in brackets):
|
(dependent HAPI modules listed in brackets):
|
||||||
<ul>
|
<ul>
|
||||||
<li>Hibernate ORM (JPA): 5.4.6 -> 5.4.10</li>
|
<li>Hibernate ORM (JPA): 5.4.6.Final -> 5.4.11.Final</li>
|
||||||
</ul>"
|
</ul>"
|
||||||
- item:
|
- item:
|
||||||
type: change
|
type: change
|
||||||
|
|
|
@ -390,6 +390,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
ResourceTable entity = new ResourceTable();
|
ResourceTable entity = new ResourceTable();
|
||||||
entity.setResourceType(toResourceName(theResource));
|
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)) {
|
if (isNotBlank(theIfNoneExist)) {
|
||||||
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theRequest);
|
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theRequest);
|
||||||
if (match.size() > 1) {
|
if (match.size() > 1) {
|
||||||
|
@ -432,7 +445,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
|
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify JPA interceptors
|
// Interceptor call: STORAGE_PRESTORAGE_RESOURCE_CREATED
|
||||||
HookParams hookParams = new HookParams()
|
HookParams hookParams = new HookParams()
|
||||||
.add(IBaseResource.class, theResource)
|
.add(IBaseResource.class, theResource)
|
||||||
.add(RequestDetails.class, theRequest)
|
.add(RequestDetails.class, theRequest)
|
||||||
|
|
|
@ -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_ERROR;
|
||||||
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_INFO;
|
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.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public abstract class BaseStorageDao {
|
public abstract class BaseStorageDao {
|
||||||
|
@ -161,6 +160,10 @@ public abstract class BaseStorageDao {
|
||||||
JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequestDetails, thePointcut, theParams);
|
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();
|
protected abstract IInterceptorBroadcaster getInterceptorBroadcaster();
|
||||||
|
|
||||||
public IBaseOperationOutcome createErrorOperationOutcome(String theMessage, String theCode) {
|
public IBaseOperationOutcome createErrorOperationOutcome(String theMessage, String theCode) {
|
||||||
|
|
|
@ -183,6 +183,11 @@ public class DaoConfig {
|
||||||
*/
|
*/
|
||||||
private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets;
|
private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
private boolean myMultiTenancyEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@ -1907,7 +1912,25 @@ public class DaoConfig {
|
||||||
setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount()));
|
setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StoreMetaSourceInformationEnum {
|
/**
|
||||||
|
* If enabled (default is <code>false</code>) the JPA server will support multitenant queries
|
||||||
|
*
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
public void setMultiTenancyEnabled(boolean theMultiTenancyEnabled) {
|
||||||
|
myMultiTenancyEnabled = theMultiTenancyEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled (default is <code>false</code>) the JPA server will support multitenant queries
|
||||||
|
*
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
public boolean isMultiTenancyEnabled() {
|
||||||
|
return myMultiTenancyEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StoreMetaSourceInformationEnum {
|
||||||
NONE(false, false),
|
NONE(false, false),
|
||||||
SOURCE_URI(true, false),
|
SOURCE_URI(true, false),
|
||||||
REQUEST_ID(false, true),
|
REQUEST_ID(false, true),
|
||||||
|
|
|
@ -70,6 +70,7 @@ public class DaoSearchParamSynchronizer {
|
||||||
theEntity.getParamsQuantity().remove(next);
|
theEntity.getParamsQuantity().remove(next);
|
||||||
}
|
}
|
||||||
for (T next : quantitiesToAdd) {
|
for (T next : quantitiesToAdd) {
|
||||||
|
next.setTenantId(theEntity.getTenantId());
|
||||||
myEntityManager.merge(next);
|
myEntityManager.merge(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,84 +1,31 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
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.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
|
import ca.uhn.fhir.jpa.model.entity.TenantId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
|
|
||||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
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 ca.uhn.fhir.util.TestUtil;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hamcrest.Matchers;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
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.After;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
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 javax.servlet.ServletException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertNotEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
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 {
|
public class MultitenantR4Test extends BaseJpaR4SystemTest {
|
||||||
|
|
||||||
|
@ -86,10 +33,17 @@ public class MultitenantR4Test extends BaseJpaR4SystemTest {
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void after() {
|
public void after() {
|
||||||
|
myDaoConfig.setMultiTenancyEnabled(new DaoConfig().isMultiTenancyEnabled());
|
||||||
|
|
||||||
|
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@Before
|
@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());
|
p.setBirthDate(new Date());
|
||||||
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
|
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
|
||||||
|
|
||||||
runInTransaction(()->{
|
runInTransaction(() -> {
|
||||||
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(() -> new IllegalArgumentException());
|
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
|
||||||
assertNull(resourceTable.getTenantId());
|
assertNull(resourceTable.getTenantId());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateResourceWithTenant() {
|
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();
|
Patient p = new Patient();
|
||||||
p.setUserData(JpaConstants.USERDATA_TENANT_ID, 3);
|
p.addName().setFamily("FAM");
|
||||||
p.setUserData(JpaConstants.USERDATA_TENANT_DATE, LocalDate.of(2020, Month.JANUARY, 14));
|
|
||||||
p.addIdentifier().setSystem("system").setValue("value");
|
p.addIdentifier().setSystem("system").setValue("value");
|
||||||
p.setBirthDate(new Date());
|
p.setBirthDate(new Date());
|
||||||
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
|
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
|
||||||
|
|
||||||
runInTransaction(()->{
|
runInTransaction(() -> {
|
||||||
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(() -> new IllegalArgumentException());
|
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
|
||||||
assertNull(resourceTable.getTenantId());
|
assertEquals(expectId, resourceTable.getTenantId().getTenantId().intValue());
|
||||||
|
assertEquals(expectDate, resourceTable.getTenantId().getTenantDate());
|
||||||
|
|
||||||
|
List<ResourceIndexedSearchParamString> 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<TenantId> 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
|
@AfterClass
|
||||||
public static void afterClassClearContext() {
|
public static void afterClassClearContext() {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
|
|
@ -20,10 +20,24 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import javax.persistence.Embedded;
|
||||||
|
import javax.persistence.MappedSuperclass;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@MappedSuperclass
|
||||||
public abstract class BaseResourceIndex implements Serializable {
|
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 Long getId();
|
||||||
|
|
||||||
public abstract void setId(Long theId);
|
public abstract void setId(Long theId);
|
||||||
|
|
|
@ -80,17 +80,6 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
private Date myUpdated;
|
private Date myUpdated;
|
||||||
|
|
||||||
@Embedded
|
|
||||||
private TenantId myTenantId;
|
|
||||||
|
|
||||||
public TenantId getTenantId() {
|
|
||||||
return myTenantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTenantId(TenantId theTenantId) {
|
|
||||||
myTenantId = theTenantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclasses may override
|
* Subclasses may override
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -221,6 +221,13 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
@Transient
|
@Transient
|
||||||
private transient ResourceHistoryTable myCurrentVersionEntity;
|
private transient ResourceHistoryTable myCurrentVersionEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public ResourceTable() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResourceTag addTag(TagDefinition theTag) {
|
public ResourceTag addTag(TagDefinition theTag) {
|
||||||
for (ResourceTag next : getTags()) {
|
for (ResourceTag next : getTags()) {
|
||||||
|
@ -423,6 +430,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Collection<ResourceTag> getTags() {
|
public Collection<ResourceTag> getTags() {
|
||||||
if (myTags == null) {
|
if (myTags == null) {
|
||||||
myTags = new HashSet<>();
|
myTags = new HashSet<>();
|
||||||
|
@ -551,6 +559,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
retVal.setFhirVersion(getFhirVersion());
|
retVal.setFhirVersion(getFhirVersion());
|
||||||
retVal.setDeleted(getDeleted());
|
retVal.setDeleted(getDeleted());
|
||||||
retVal.setForcedId(getForcedId());
|
retVal.setForcedId(getForcedId());
|
||||||
|
retVal.setTenantId(getTenantId());
|
||||||
|
|
||||||
retVal.getTags().clear();
|
retVal.getTags().clear();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.model.entity;
|
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.Column;
|
||||||
import javax.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
@ -12,7 +15,22 @@ public class TenantId implements Cloneable {
|
||||||
@Column(name = "TENANT_DATE", nullable = true)
|
@Column(name = "TENANT_DATE", nullable = true)
|
||||||
private LocalDate myTenantDate;
|
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;
|
return myTenantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,4 +54,12 @@ public class TenantId implements Cloneable {
|
||||||
.setTenantId(getTenantId())
|
.setTenantId(getTenantId())
|
||||||
.setTenantDate(getTenantDate());
|
.setTenantDate(getTenantDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||||
|
.append("id", myTenantId)
|
||||||
|
.append("date", myTenantDate)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,6 @@ import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
|
||||||
public class JpaConstants {
|
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
|
* Operation name for the $apply-codesystem-delta-add operation
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,6 +9,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||||
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
|
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
|
||||||
import org.apache.commons.dbcp2.BasicDataSource;
|
import org.apache.commons.dbcp2.BasicDataSource;
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.hibernate.dialect.PostgreSQL94Dialect;
|
import org.hibernate.dialect.PostgreSQL94Dialect;
|
||||||
import org.hl7.fhir.dstu2.model.Subscription;
|
import org.hl7.fhir.dstu2.model.Subscription;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
@ -94,6 +95,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
|
||||||
retVal.setUsername(myDbUsername);
|
retVal.setUsername(myDbUsername);
|
||||||
retVal.setPassword(myDbPassword);
|
retVal.setPassword(myDbPassword);
|
||||||
retVal.setDefaultQueryTimeout(20);
|
retVal.setDefaultQueryTimeout(20);
|
||||||
|
retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||||
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
|
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
|
||||||
import org.apache.commons.dbcp2.BasicDataSource;
|
import org.apache.commons.dbcp2.BasicDataSource;
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.hibernate.dialect.PostgreSQL94Dialect;
|
import org.hibernate.dialect.PostgreSQL94Dialect;
|
||||||
import org.hl7.fhir.dstu2.model.Subscription;
|
import org.hl7.fhir.dstu2.model.Subscription;
|
||||||
import org.springframework.beans.factory.annotation.Autowire;
|
import org.springframework.beans.factory.annotation.Autowire;
|
||||||
|
@ -101,6 +102,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
||||||
retVal.setUsername(myDbUsername);
|
retVal.setUsername(myDbUsername);
|
||||||
retVal.setPassword(myDbPassword);
|
retVal.setPassword(myDbPassword);
|
||||||
retVal.setDefaultQueryTimeout(20);
|
retVal.setDefaultQueryTimeout(20);
|
||||||
|
retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||||
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
|
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
|
||||||
import org.apache.commons.dbcp2.BasicDataSource;
|
import org.apache.commons.dbcp2.BasicDataSource;
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.hibernate.dialect.PostgreSQL94Dialect;
|
import org.hibernate.dialect.PostgreSQL94Dialect;
|
||||||
import org.hl7.fhir.dstu2.model.Subscription;
|
import org.hl7.fhir.dstu2.model.Subscription;
|
||||||
import org.springframework.beans.factory.annotation.Autowire;
|
import org.springframework.beans.factory.annotation.Autowire;
|
||||||
|
@ -86,6 +87,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
||||||
retVal.setUsername(myDbUsername);
|
retVal.setUsername(myDbUsername);
|
||||||
retVal.setPassword(myDbPassword);
|
retVal.setPassword(myDbPassword);
|
||||||
retVal.setDefaultQueryTimeout(20);
|
retVal.setDefaultQueryTimeout(20);
|
||||||
|
retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||||
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
|
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
|
||||||
import org.apache.commons.dbcp2.BasicDataSource;
|
import org.apache.commons.dbcp2.BasicDataSource;
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.hibernate.dialect.PostgreSQL94Dialect;
|
import org.hibernate.dialect.PostgreSQL94Dialect;
|
||||||
import org.hl7.fhir.dstu2.model.Subscription;
|
import org.hl7.fhir.dstu2.model.Subscription;
|
||||||
import org.springframework.beans.factory.annotation.Autowire;
|
import org.springframework.beans.factory.annotation.Autowire;
|
||||||
|
@ -86,6 +87,7 @@ public class TestR5Config extends BaseJavaConfigR5 {
|
||||||
retVal.setUsername(myDbUsername);
|
retVal.setUsername(myDbUsername);
|
||||||
retVal.setPassword(myDbPassword);
|
retVal.setPassword(myDbPassword);
|
||||||
retVal.setDefaultQueryTimeout(20);
|
retVal.setDefaultQueryTimeout(20);
|
||||||
|
retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
pom.xml
4
pom.xml
|
@ -645,7 +645,7 @@
|
||||||
<jsr305_version>3.0.2</jsr305_version>
|
<jsr305_version>3.0.2</jsr305_version>
|
||||||
<flyway_version>6.1.0</flyway_version>
|
<flyway_version>6.1.0</flyway_version>
|
||||||
<!--<hibernate_version>5.2.10.Final</hibernate_version>-->
|
<!--<hibernate_version>5.2.10.Final</hibernate_version>-->
|
||||||
<hibernate_version>5.4.10.Final</hibernate_version>
|
<hibernate_version>5.4.11.Final</hibernate_version>
|
||||||
<!-- Update lucene version when you update hibernate-search version -->
|
<!-- Update lucene version when you update hibernate-search version -->
|
||||||
<hibernate_search_version>5.11.3.Final</hibernate_search_version>
|
<hibernate_search_version>5.11.3.Final</hibernate_search_version>
|
||||||
<lucene_version>5.5.5</lucene_version>
|
<lucene_version>5.5.5</lucene_version>
|
||||||
|
@ -1326,7 +1326,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<version>42.2.9</version>
|
<version>42.2.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.quartz-scheduler</groupId>
|
<groupId>org.quartz-scheduler</groupId>
|
||||||
|
|
Loading…
Reference in New Issue