Work on multitenancy
This commit is contained in:
parent
691f2c4e9a
commit
62d867902f
|
@ -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 {
|
|||
),
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* <b>Server Hook:</b>
|
||||
* This method is called after the server implementation method has been called, but before any attempt
|
||||
|
@ -1323,14 +1326,16 @@ public enum Pointcut {
|
|||
|
||||
/**
|
||||
* <b>Storage Hook:</b>
|
||||
* Invoked before an <code>$expunge</code> operation on all data (expungeEverything) is called.
|
||||
* Invoked before FHIR <b>create</b> 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.
|
||||
* <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:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>
|
||||
* org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned.
|
||||
* </li>
|
||||
* <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
|
||||
|
@ -1346,18 +1351,53 @@ public enum Pointcut {
|
|||
* </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Hooks should return an instance of <code>ca.uhn.fhir.jpa.model.entity.TenantId</code> or <code>null</code>.
|
||||
* Hooks should return an instance of <code>ca.uhn.fhir.jpa.model.entity.PartitionId</code> or <code>null</code>.
|
||||
* </p>
|
||||
*/
|
||||
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"
|
||||
),
|
||||
|
||||
/**
|
||||
* <b>Storage Hook:</b>
|
||||
* Invoked before FHIR read/access operation (e.g. <b>read/vread</b>, <b>search</b>, <b>history</b>, 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.
|
||||
* <p>
|
||||
* Hooks may accept the following parameters:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <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.PartitionId</code> or <code>null</code>.
|
||||
* </p>
|
||||
*/
|
||||
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"
|
||||
),
|
||||
|
||||
/**
|
||||
* <b>Performance Tracing Hook:</b>
|
||||
* This hook is invoked when any informational messages generated by the
|
||||
|
@ -1680,12 +1720,12 @@ public enum Pointcut {
|
|||
* </p>
|
||||
* <p>
|
||||
* THIS IS AN EXPERIMENTAL HOOK AND MAY BE REMOVED OR CHANGED WITHOUT WARNING.
|
||||
* </p>
|
||||
* <p>
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this is a performance tracing hook. Use with caution in production
|
||||
* systems, since calling it may (or may not) carry a cost.
|
||||
* </p>
|
||||
* <p>
|
||||
* </p>
|
||||
* <p>
|
||||
* Hooks may accept the following parameters:
|
||||
* </p>
|
||||
* <ul>
|
||||
|
@ -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<String> myParameterTypes;
|
||||
private final Class<?> myReturnType;
|
||||
|
|
|
@ -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}"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<ul>
|
||||
<li>Hibernate ORM (JPA): 5.4.6 -> 5.4.12</li>
|
||||
<li>Hibernate Search (JPA): 5.11.3 -> 5.11.5</li>
|
||||
<li>Guava (JPA): 28.0 -> 28.2</li>
|
||||
</ul>"
|
||||
- item:
|
||||
issue: "1583"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -219,7 +219,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> 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<T extends IBaseResource> 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));
|
||||
}
|
||||
|
|
|
@ -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<T extends IBaseResource> extends B
|
|||
private IInstanceValidatorModule myInstanceValidator;
|
||||
private String myResourceName;
|
||||
private Class<T> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
|
||||
|
||||
return readEntity(theId, true, theRequest);
|
||||
}
|
||||
|
||||
|
@ -952,9 +989,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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
|
||||
|
|
|
@ -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 <code>false</code>) the JPA server will support multitenant queries
|
||||
* If enabled (default is <code>false</code>) 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 <code>false</code>) the JPA server will support multitenant queries
|
||||
* If enabled (default is <code>false</code>) the JPA server will support data partitioning
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public boolean isMultiTenancyEnabled() {
|
||||
return myMultiTenancyEnabled;
|
||||
public boolean isPartitioningEnabled() {
|
||||
return myPartitioningEnabled;
|
||||
}
|
||||
|
||||
public enum StoreMetaSourceInformationEnum {
|
||||
|
|
|
@ -50,7 +50,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Su
|
|||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId);
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -22,7 +22,9 @@ package ca.uhn.fhir.jpa.dao;
|
|||
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
|
||||
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.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
@ -269,6 +271,9 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
|||
return doSearch(theResourceName, theParams, null);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private RequestPartitionHelperService myRequestPartitionHelperService;
|
||||
|
||||
@Transactional()
|
||||
@Override
|
||||
public List<Suggestion> 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);
|
||||
|
||||
|
|
|
@ -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<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, PartitionId thePartitionId);
|
||||
|
||||
void setMaxResultsToFetch(Integer theMaxResultsToFetch);
|
||||
|
||||
Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest);
|
||||
|
||||
void loadResourcesByPid(Collection<ResourcePersistentId> thePids, Collection<ResourcePersistentId> theIncludedPids, List<IBaseResource> theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails);
|
||||
|
||||
Set<ResourcePersistentId> loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<ResourcePersistentId> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
|
||||
|
|
|
@ -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<ResourcePersistentId> 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<List<IQueryParameterType>> 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<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest) {
|
||||
init(theParams, theSearchUuid);
|
||||
public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||
init(theParams, theSearchUuid, thePartitionId);
|
||||
|
||||
TypedQuery<Long> 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<Long> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) {
|
||||
CriteriaQuery<Long> 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
|
||||
|
|
|
@ -39,6 +39,9 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
|
|||
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id")
|
||||
Optional<Long> 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<Long> 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);
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends BaseHapiFhirResourceDao<Su
|
|||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId);
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -70,7 +70,7 @@ public class DaoSearchParamSynchronizer {
|
|||
theEntity.getParamsQuantity().remove(next);
|
||||
}
|
||||
for (T next : quantitiesToAdd) {
|
||||
next.setTenantId(theEntity.getTenantId());
|
||||
next.setPartitionId(theEntity.getPartitionId());
|
||||
myEntityManager.merge(next);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
|||
import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.PartitionId;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
|
@ -51,6 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -59,6 +61,7 @@ import java.util.Date;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -132,14 +135,14 @@ public class IdHelperService {
|
|||
* @throws ResourceNotFoundException If the ID can not be found
|
||||
*/
|
||||
@Nonnull
|
||||
public ResourcePersistentId resolveResourcePersistentIds(String theResourceType, String theId) {
|
||||
public ResourcePersistentId resolveResourcePersistentIds(PartitionId thePartitionId, String theResourceType, String theId) {
|
||||
Long retVal;
|
||||
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
|
||||
if (myDaoConfig.isDeleteEnabled()) {
|
||||
retVal = resolveResourceIdentity(theResourceType, theId);
|
||||
retVal = resolveResourceIdentity(thePartitionId, theResourceType, theId);
|
||||
} else {
|
||||
String key = theResourceType + "/" + theId;
|
||||
retVal = myPersistentIdCache.get(key, t -> 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<Long> 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<IResourceLookup> translateForcedIdToPids(RequestDetails theRequest, Collection<IIdType> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Object> 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 <code>STORAGE_PARTITION_IDENTIFY_READ</code> 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 <code>STORAGE_PARTITION_IDENTIFY_CREATE</code> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin) {
|
||||
|
||||
void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> 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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -91,24 +91,24 @@ public class PredicateBuilder {
|
|||
return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation);
|
||||
}
|
||||
|
||||
public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) {
|
||||
myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest);
|
||||
public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||
myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, thePartitionId);
|
||||
}
|
||||
|
||||
Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest) {
|
||||
return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest);
|
||||
Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||
return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest, thePartitionId);
|
||||
}
|
||||
|
||||
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<ResourceTable, ResourceLink> theJoin) {
|
||||
return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin);
|
||||
}
|
||||
|
||||
void addPredicateResourceId(List<List<IQueryParameterType>> theAndOrParams, String theResourceName, RequestDetails theRequest) {
|
||||
myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, theRequest);
|
||||
void addPredicateResourceId(List<List<IQueryParameterType>> theAndOrParams, String theResourceName, PartitionId thePartitionId) {
|
||||
myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, thePartitionId);
|
||||
}
|
||||
|
||||
public Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
|
||||
return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequest);
|
||||
public Predicate addPredicateResourceId(List<List<IQueryParameterType>> 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<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theStringJoin) {
|
||||
|
|
|
@ -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<ResourceTable, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest) {
|
||||
private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, Join<ResourceTable, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||
final List<Class<? extends IBaseResource>> resourceTypes;
|
||||
if (!theReferenceParam.hasResourceType()) {
|
||||
|
||||
|
@ -384,7 +384,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
|||
}
|
||||
|
||||
|
||||
Subquery<Long> subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest);
|
||||
Subquery<Long> 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<Long> createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List<IQueryParameterType> theOrValues, RequestDetails theRequest) {
|
||||
Subquery<Long> createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List<IQueryParameterType> theOrValues, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||
Subquery<Long> 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<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) {
|
||||
void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<IQueryParameterType>> 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<Predicate> 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<List<IQueryParameterType>> theHasParameters, RequestDetails theRequest) {
|
||||
private void addPredicateHas(String theResourceType, List<List<IQueryParameterType>> 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<Long> subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest);
|
||||
Subquery<Long> subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest, thePartitionId);
|
||||
Join<ResourceTable, ResourceLink> join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
|
||||
|
||||
Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join);
|
||||
|
|
|
@ -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<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
|
||||
Predicate addPredicateResourceId(List<List<IQueryParameterType>> 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<ResourceTable> theRoot, String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation) {
|
||||
private Predicate createPredicate(Root<ResourceTable> theRoot, String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
|
||||
Predicate nextPredicate = null;
|
||||
|
||||
Set<ResourcePersistentId> 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
|
||||
|
|
|
@ -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<ResourceTable, ResourceIndexedSearchParamUri> 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<Predicate> 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) {
|
||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
|
|||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId);
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
|
|||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId);
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -37,7 +37,7 @@ public interface ISearchCoordinatorSvc {
|
|||
|
||||
List<ResourcePersistentId> 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
|
||||
|
|
|
@ -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<ResourcePersistentId> 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<ResourcePersistentId> 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<Long> countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest);
|
||||
Iterator<Long> 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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<TermConcept, Object> theConceptsStack, int theTotalConcepts) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
|
||||
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
|
||||
assertEquals(10, strings.size());
|
||||
assertEquals(myTenantId, strings.get(0).getTenantId().getTenantId().intValue());
|
||||
assertEquals(myTenantDate, strings.get(0).getTenantId().getTenantDate());
|
||||
|
||||
// HFJ_SPIDX_DATE
|
||||
List<ResourceIndexedSearchParamDate> dates = myResourceIndexedSearchParamDateDao.findAllForResourceId(patientId);
|
||||
ourLog.info("\n * {}", dates.stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * ")));
|
||||
assertEquals(2, dates.size());
|
||||
assertEquals(myTenantId, dates.get(0).getTenantId().getTenantId().intValue());
|
||||
assertEquals(myTenantDate, dates.get(0).getTenantId().getTenantDate());
|
||||
assertEquals(myTenantId, dates.get(1).getTenantId().getTenantId().intValue());
|
||||
assertEquals(myTenantDate, dates.get(1).getTenantId().getTenantDate());
|
||||
|
||||
// HFJ_RES_LINK
|
||||
List<ResourceLink> 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<SearchParamPresent> 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<ResourceIndexedCompositeStringUnique> 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<ForcedId> forcedIds = myForcedIdDao.findAll();
|
||||
assertEquals(2, forcedIds.size());
|
||||
assertEquals(myTenantId, forcedIds.get(0).getTenantId().getTenantId().intValue());
|
||||
assertEquals(myTenantDate, forcedIds.get(0).getTenantId().getTenantDate());
|
||||
assertEquals(myTenantId, forcedIds.get(1).getTenantId().getTenantId().intValue());
|
||||
assertEquals(myTenantDate, forcedIds.get(1).getTenantId().getTenantDate());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
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<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
|
||||
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
|
||||
assertEquals(10, strings.size());
|
||||
assertEquals(myTenantId, strings.get(0).getTenantId().getTenantId().intValue());
|
||||
assertEquals(myTenantDate, strings.get(0).getTenantId().getTenantDate());
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadAcrossTenants() {
|
||||
IIdType patientId1 = createPatient(1, withActiveTrue());
|
||||
IIdType patientId2 = createPatient(1, withActiveTrue());
|
||||
|
||||
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
|
||||
assertEquals(patientId1, gotId1);
|
||||
IdType gotId2 = myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
|
||||
assertEquals(patientId2, gotId2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchAcrossAllTenants() {
|
||||
|
||||
}
|
||||
|
||||
private void createUniqueCompositeSp() {
|
||||
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<Patient>... theModifiers) {
|
||||
addCreateTenant(theTenantId, null);
|
||||
Patient p = new Patient();
|
||||
for (Consumer<Patient> next : theModifiers) {
|
||||
next.accept(p);
|
||||
}
|
||||
|
||||
return myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
public void createRequestId() {
|
||||
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
|
||||
}
|
||||
|
||||
private Consumer<Patient> withActiveTrue() {
|
||||
return t->t.setActive(true);
|
||||
}
|
||||
|
||||
@Interceptor
|
||||
public static class MyInterceptor {
|
||||
|
||||
|
||||
private final List<TenantId> 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ResourceTag> 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<ResourceHistoryTag> 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<ResourceIndexedSearchParamString> 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<ResourceIndexedSearchParamDate> 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<ResourceLink> 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<SearchParamPresent> 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<ResourceIndexedCompositeStringUnique> 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<ForcedId> 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<ResourceHistoryTag> 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<ResourceIndexedSearchParamString> 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<IIdType> 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<IIdType> 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<Patient>... theModifiers) {
|
||||
if (thePartitionId != null) {
|
||||
addCreateTenant(thePartitionId, null);
|
||||
}
|
||||
|
||||
Patient p = new Patient();
|
||||
for (Consumer<Patient> next : theModifiers) {
|
||||
next.accept(p);
|
||||
}
|
||||
|
||||
return myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
public void createRequestId() {
|
||||
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
|
||||
}
|
||||
|
||||
private Consumer<Patient> withActiveTrue() {
|
||||
return t -> t.setActive(true);
|
||||
}
|
||||
|
||||
private Consumer<Patient> withId(String theId) {
|
||||
return t -> {
|
||||
assertThat(theId, matchesPattern("[a-zA-Z0-9]+"));
|
||||
t.setId("Patient/" + theId);
|
||||
};
|
||||
}
|
||||
|
||||
@Interceptor
|
||||
public static class MyInterceptor {
|
||||
|
||||
|
||||
private final List<PartitionId> myCreatePartitionIds = new ArrayList<>();
|
||||
private final List<PartitionId> 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ResourcePersistentId> 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<ResourcePersistentId> 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<ResourcePersistentId> 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<ResourcePersistentId> 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<ResourcePersistentId> 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<ResourcePersistentId> 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<ResourcePersistentId> 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<ResourcePersistentId> 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));
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -49,7 +49,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
|||
@Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH)
|
||||
private String myIndexString;
|
||||
@Embedded
|
||||
private TenantId myTenantId;
|
||||
private PartitionId myPartitionId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -64,15 +64,15 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
|||
public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) {
|
||||
setResource(theResource);
|
||||
setIndexString(theIndexString);
|
||||
setTenantId(theResource.getTenantId());
|
||||
setPartitionId(theResource.getPartitionId());
|
||||
}
|
||||
|
||||
public TenantId getTenantId() {
|
||||
return myTenantId;
|
||||
public PartitionId getPartitionId() {
|
||||
return myPartitionId;
|
||||
}
|
||||
|
||||
public void setTenantId(TenantId theTenantId) {
|
||||
myTenantId = theTenantId;
|
||||
public void setPartitionId(PartitionId thePartitionId) {
|
||||
myPartitionId = thePartitionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -127,7 +127,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
|||
.append("id", myId)
|
||||
.append("resourceId", myResourceId)
|
||||
.append("indexString", myIndexString)
|
||||
.append("tenant", myTenantId)
|
||||
.append("tenant", myPartitionId)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -242,7 +242,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
return next;
|
||||
}
|
||||
}
|
||||
ResourceTag tag = new ResourceTag(this, theTag);
|
||||
ResourceTag tag = new ResourceTag(this, theTag, getPartitionId());
|
||||
getTags().add(tag);
|
||||
return tag;
|
||||
}
|
||||
|
@ -556,7 +556,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
retVal.setDeleted(getDeleted());
|
||||
retVal.setResourceTable(this);
|
||||
retVal.setForcedId(getForcedId());
|
||||
retVal.setTenantId(getTenantId());
|
||||
retVal.setPartitionId(getPartitionId());
|
||||
|
||||
retVal.getTags().clear();
|
||||
|
||||
|
|
|
@ -51,21 +51,15 @@ public class ResourceTag extends BaseTag {
|
|||
@Column(name = "RES_ID", insertable = false, updatable = false)
|
||||
private Long myResourceId;
|
||||
|
||||
@Embedded
|
||||
private TenantId myTenantId;
|
||||
|
||||
public ResourceTag() {
|
||||
}
|
||||
|
||||
public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag) {
|
||||
public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag, PartitionId thePartitionId) {
|
||||
setTag(theTag);
|
||||
setResource(theResourceTable);
|
||||
setResourceId(theResourceTable.getId());
|
||||
setResourceType(theResourceTable.getResourceType());
|
||||
}
|
||||
|
||||
public void setTenantId(TenantId theTenantId) {
|
||||
myTenantId = theTenantId;
|
||||
setPartitionId(thePartitionId);
|
||||
}
|
||||
|
||||
public Long getResourceId() {
|
||||
|
|
|
@ -53,7 +53,7 @@ public class SearchParamPresent implements Serializable {
|
|||
@Column(name = "HASH_PRESENCE")
|
||||
private Long myHashPresence;
|
||||
@Embedded
|
||||
private TenantId myTenantId;
|
||||
private PartitionId myPartitionId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -112,16 +112,16 @@ public class SearchParamPresent implements Serializable {
|
|||
b.append("resPid", myResource.getIdDt().toUnqualifiedVersionless().getValue());
|
||||
b.append("paramName", myParamName);
|
||||
b.append("present", myPresent);
|
||||
b.append("tenant", myTenantId);
|
||||
b.append("tenant", myPartitionId);
|
||||
return b.build();
|
||||
}
|
||||
|
||||
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 static long calculateHashPresence(String theResourceType, String theParamName, Boolean thePresent) {
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.model.entity;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
|
||||
@Embeddable
|
||||
public class TenantId implements Cloneable {
|
||||
|
||||
@Column(name = "TENANT_ID", nullable = true, insertable = true, updatable = false)
|
||||
private Integer myTenantId;
|
||||
@Column(name = "TENANT_DATE", nullable = true, insertable = true, updatable = false)
|
||||
private LocalDate myTenantDate;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public TenantId() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public TenantId(int theTenantId, LocalDate theTenantDate) {
|
||||
setTenantId(theTenantId);
|
||||
setTenantDate(theTenantDate);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return defaultIfNull(myTenantId, "null").toString();
|
||||
}
|
||||
}
|
2
pom.xml
2
pom.xml
|
@ -637,7 +637,7 @@
|
|||
<!--<derby_version>10.15.1.3</derby_version>-->
|
||||
<error_prone_annotations_version>2.3.4</error_prone_annotations_version>
|
||||
<error_prone_core_version>2.3.3</error_prone_core_version>
|
||||
<guava_version>28.0-jre</guava_version>
|
||||
<guava_version>28.2-jre</guava_version>
|
||||
<gson_version>2.8.5</gson_version>
|
||||
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
||||
<jaxb_api_version>2.3.1</jaxb_api_version>
|
||||
|
|
Loading…
Reference in New Issue