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 ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
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()}
|
* Value for {@link Hook#value()}
|
||||||
|
@ -374,7 +378,6 @@ public enum Pointcut {
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>Server Hook:</b>
|
* <b>Server Hook:</b>
|
||||||
* This method is called after the server implementation method has been called, but before any attempt
|
* 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>
|
* <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>
|
* <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:
|
* Hooks may accept the following parameters:
|
||||||
|
* </p>
|
||||||
* <ul>
|
* <ul>
|
||||||
|
* <li>
|
||||||
* org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned.
|
* org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned.
|
||||||
|
* </li>
|
||||||
* <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
|
* 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
|
* 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>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <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>
|
* </p>
|
||||||
*/
|
*/
|
||||||
STORAGE_TENANT_IDENTIFY_CREATE (
|
STORAGE_PARTITION_IDENTIFY_CREATE(
|
||||||
// Return type
|
// Return type
|
||||||
"ca.uhn.fhir.jpa.model.entity.TenantId",
|
"ca.uhn.fhir.jpa.model.entity.PartitionId",
|
||||||
// Params
|
// Params
|
||||||
"org.hl7.fhir.instance.model.api.IBaseResource",
|
"org.hl7.fhir.instance.model.api.IBaseResource",
|
||||||
"ca.uhn.fhir.rest.api.server.RequestDetails",
|
"ca.uhn.fhir.rest.api.server.RequestDetails",
|
||||||
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
"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>
|
* <b>Performance Tracing Hook:</b>
|
||||||
* This hook is invoked when any informational messages generated by the
|
* This hook is invoked when any informational messages generated by the
|
||||||
|
@ -1680,12 +1720,12 @@ public enum Pointcut {
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* THIS IS AN EXPERIMENTAL HOOK AND MAY BE REMOVED OR CHANGED WITHOUT WARNING.
|
* 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
|
* Note that this is a performance tracing hook. Use with caution in production
|
||||||
* systems, since calling it may (or may not) carry a cost.
|
* systems, since calling it may (or may not) carry a cost.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* Hooks may accept the following parameters:
|
* Hooks may accept the following parameters:
|
||||||
* </p>
|
* </p>
|
||||||
* <ul>
|
* <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
|
* This pointcut is used only for unit tests. Do not use in production code as it may be changed or
|
||||||
* removed at any time.
|
* 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 List<String> myParameterTypes;
|
||||||
private final Class<?> myReturnType;
|
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.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.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}"
|
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"contactName": "Kevin Mayfield",
|
"contactName": "Kevin Mayfield",
|
||||||
"contactEmail": "Kevin.mayfield@mayfield-is.co.uk",
|
"contactEmail": "Kevin.mayfield@mayfield-is.co.uk",
|
||||||
"link": "https://data.developer.nhs.uk/ccri/exp",
|
"link": "https://data.developer.nhs.uk/ccri/exp",
|
||||||
|
"city": "UK",
|
||||||
"lat": 55.378052,
|
"lat": 55.378052,
|
||||||
"lon": -3.435973,
|
"lon": -3.435973,
|
||||||
"added": "2019-08-19"
|
"added": "2019-08-19"
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
"contactName": "David Hay",
|
"contactName": "David Hay",
|
||||||
"contactEmail": "david.hay25@gmail.com",
|
"contactEmail": "david.hay25@gmail.com",
|
||||||
"link": "http://clinfhir.com",
|
"link": "http://clinfhir.com",
|
||||||
|
"city": "New Zealand",
|
||||||
"lat": -42.651737,
|
"lat": -42.651737,
|
||||||
"lon": 171.926909,
|
"lon": 171.926909,
|
||||||
"added": "2019-08-18"
|
"added": "2019-08-18"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li>Hibernate ORM (JPA): 5.4.6 -> 5.4.12</li>
|
<li>Hibernate ORM (JPA): 5.4.6 -> 5.4.12</li>
|
||||||
<li>Hibernate Search (JPA): 5.11.3 -> 5.11.5</li>
|
<li>Hibernate Search (JPA): 5.11.3 -> 5.11.5</li>
|
||||||
|
<li>Guava (JPA): 28.0 -> 28.2</li>
|
||||||
</ul>"
|
</ul>"
|
||||||
- item:
|
- item:
|
||||||
issue: "1583"
|
issue: "1583"
|
||||||
|
|
|
@ -44,6 +44,7 @@ page.server_jpa.architecture=Architecture
|
||||||
page.server_jpa.configuration=Configuration
|
page.server_jpa.configuration=Configuration
|
||||||
page.server_jpa.search=Search
|
page.server_jpa.search=Search
|
||||||
page.server_jpa.performance=Performance
|
page.server_jpa.performance=Performance
|
||||||
|
page.server_jpa.partitioning=Partitioning
|
||||||
page.server_jpa.upgrading=Upgrade Guide
|
page.server_jpa.upgrading=Upgrade Guide
|
||||||
|
|
||||||
section.interceptors.title=Interceptors
|
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));
|
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);
|
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.BulkDataExportSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
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.graphql.JpaStorageServices;
|
||||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
||||||
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
||||||
|
@ -231,6 +232,11 @@ public abstract class BaseConfig {
|
||||||
return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer());
|
return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RequestPartitionHelperService requestPartitionHelperService() {
|
||||||
|
return new RequestPartitionHelperService();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
|
public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
|
||||||
return new PersistenceExceptionTranslationPostProcessor();
|
return new PersistenceExceptionTranslationPostProcessor();
|
||||||
|
|
|
@ -219,7 +219,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
retVal.setResourceType(theEntity.getResourceType());
|
retVal.setResourceType(theEntity.getResourceType());
|
||||||
retVal.setForcedId(theId.getIdPart());
|
retVal.setForcedId(theId.getIdPart());
|
||||||
retVal.setResource(theEntity);
|
retVal.setResource(theEntity);
|
||||||
retVal.setTenantId(theEntity.getTenantId());
|
retVal.setPartitionId(theEntity.getPartitionId());
|
||||||
theEntity.setForcedId(retVal);
|
theEntity.setForcedId(retVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1095,7 +1095,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity();
|
ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity();
|
||||||
provenance.setResourceHistoryTable(historyEntry);
|
provenance.setResourceHistoryTable(historyEntry);
|
||||||
provenance.setResourceTable(entity);
|
provenance.setResourceTable(entity);
|
||||||
provenance.setTenantId(entity.getTenantId());
|
provenance.setPartitionId(entity.getPartitionId());
|
||||||
if (haveRequestId) {
|
if (haveRequestId) {
|
||||||
provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH));
|
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.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
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.DeleteConflictList;
|
||||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
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.model.search.SearchRuntimeDetails;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
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.jpa.util.xmlpatch.XmlPatchUtils;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.*;
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import ca.uhn.fhir.rest.api.server.*;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.*;
|
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.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.util.ObjectUtil;
|
import ca.uhn.fhir.util.ObjectUtil;
|
||||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||||
import ca.uhn.fhir.util.ReflectionUtil;
|
import ca.uhn.fhir.util.ReflectionUtil;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
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.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.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Required;
|
import org.springframework.beans.factory.annotation.Required;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
@ -67,7 +103,14 @@ import javax.persistence.NoResultException;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
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;
|
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 IInstanceValidatorModule myInstanceValidator;
|
||||||
private String myResourceName;
|
private String myResourceName;
|
||||||
private Class<T> myResourceType;
|
private Class<T> myResourceType;
|
||||||
|
@Autowired
|
||||||
|
private RequestPartitionHelperService myRequestPartitionHelperService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) {
|
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());
|
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
|
@Override
|
||||||
|
@ -183,7 +229,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
validateIdPresentForDelete(theId);
|
validateIdPresentForDelete(theId);
|
||||||
validateDeleteEnabled();
|
validateDeleteEnabled();
|
||||||
|
|
||||||
final ResourceTable entity = readEntityLatestVersion(theId);
|
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||||
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
|
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
|
||||||
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
|
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();
|
StopWatch w = new StopWatch();
|
||||||
|
|
||||||
preProcessResourceForStorage(theResource);
|
preProcessResourceForStorage(theResource);
|
||||||
|
@ -398,17 +444,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
ResourceTable entity = new ResourceTable();
|
ResourceTable entity = new ResourceTable();
|
||||||
entity.setResourceType(toResourceName(theResource));
|
entity.setResourceType(toResourceName(theResource));
|
||||||
|
|
||||||
if (myDaoConfig.isMultiTenancyEnabled()) {
|
if (thePartitionId != null) {
|
||||||
// Interceptor call: STORAGE_TENANT_IDENTIFY_CREATE
|
ourLog.debug("Resource has been assigned partition ID: {}", thePartitionId);
|
||||||
HookParams params = new HookParams()
|
entity.setPartitionId(thePartitionId);
|
||||||
.add(IBaseResource.class, theResource)
|
|
||||||
.add(RequestDetails.class, theRequest)
|
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
|
||||||
TenantId tenantId = (TenantId) doCallHooksAndReturnObject(theRequest, Pointcut.STORAGE_TENANT_IDENTIFY_CREATE, params);
|
|
||||||
if (tenantId != null) {
|
|
||||||
ourLog.debug("Resource has been assigned tenant ID: {}", tenantId);
|
|
||||||
entity.setTenantId(tenantId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotBlank(theIfNoneExist)) {
|
if (isNotBlank(theIfNoneExist)) {
|
||||||
|
@ -709,7 +747,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
throw new ResourceNotFoundException(theResourceId);
|
throw new ResourceNotFoundException(theResourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId);
|
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
|
||||||
if (latestVersion.getVersion() != entity.getVersion()) {
|
if (latestVersion.getVersion() != entity.getVersion()) {
|
||||||
doMetaAdd(theMetaAdd, entity);
|
doMetaAdd(theMetaAdd, entity);
|
||||||
} else {
|
} else {
|
||||||
|
@ -741,7 +779,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
throw new ResourceNotFoundException(theResourceId);
|
throw new ResourceNotFoundException(theResourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId);
|
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
|
||||||
if (latestVersion.getVersion() != entity.getVersion()) {
|
if (latestVersion.getVersion() != entity.getVersion()) {
|
||||||
doMetaDelete(theMetaDel, entity);
|
doMetaDelete(theMetaDel, entity);
|
||||||
} else {
|
} else {
|
||||||
|
@ -817,7 +855,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
entityToUpdate = readEntityLatestVersion(theId);
|
entityToUpdate = readEntityLatestVersion(theId, theRequest);
|
||||||
if (theId.hasVersionIdPart()) {
|
if (theId.hasVersionIdPart()) {
|
||||||
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
|
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
|
||||||
throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
|
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
|
@Override
|
||||||
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
|
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
|
||||||
|
|
||||||
return readEntity(theId, true, 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) {
|
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
||||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
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());
|
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) {
|
if (entity == null) {
|
||||||
throw new ResourceNotFoundException(theId);
|
throw new ResourceNotFoundException(theId);
|
||||||
}
|
}
|
||||||
|
@ -990,8 +1041,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ResourceTable readEntityLatestVersion(IIdType theId) {
|
@NotNull
|
||||||
ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(getResourceName(), theId.getIdPart());
|
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());
|
ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId());
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new ResourceNotFoundException(theId);
|
throw new ResourceNotFoundException(theId);
|
||||||
|
@ -1127,7 +1187,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
|
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()) {
|
while (iter.hasNext()) {
|
||||||
retVal.add(iter.next());
|
retVal.add(iter.next());
|
||||||
}
|
}
|
||||||
|
@ -1231,10 +1293,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
*/
|
*/
|
||||||
resourceId = theResource.getIdElement();
|
resourceId = theResource.getIdElement();
|
||||||
|
|
||||||
|
PartitionId partitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource);
|
||||||
try {
|
try {
|
||||||
entity = readEntityLatestVersion(resourceId);
|
entity = readEntityLatestVersion(resourceId, partitionId);
|
||||||
} catch (ResourceNotFoundException e) {
|
} 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);
|
ResourceTable savedEntity = updateInternal(theRequest, theResource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource);
|
||||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource).setCreated(wasDeleted);
|
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());
|
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart());
|
||||||
outcome.setOperationOutcome(createInfoOperationOutcome(msg));
|
outcome.setOperationOutcome(createInfoOperationOutcome(msg));
|
||||||
|
|
||||||
|
@ -1304,7 +1363,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
if (theId == null || theId.hasIdPart() == false) {
|
if (theId == null || theId.hasIdPart() == false) {
|
||||||
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
|
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
|
// Validate that there are no resources pointing to the candidate that
|
||||||
// would prevent deletion
|
// would prevent deletion
|
||||||
|
|
|
@ -191,7 +191,7 @@ public class DaoConfig {
|
||||||
/**
|
/**
|
||||||
* @since 5.0.0
|
* @since 5.0.0
|
||||||
*/
|
*/
|
||||||
private boolean myMultiTenancyEnabled;
|
private boolean myPartitioningEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* 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
|
* @since 5.0.0
|
||||||
*/
|
*/
|
||||||
public void setMultiTenancyEnabled(boolean theMultiTenancyEnabled) {
|
public void setPartitioningEnabled(boolean theMultiTenancyEnabled) {
|
||||||
myMultiTenancyEnabled = 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
|
* @since 5.0.0
|
||||||
*/
|
*/
|
||||||
public boolean isMultiTenancyEnabled() {
|
public boolean isPartitioningEnabled() {
|
||||||
return myMultiTenancyEnabled;
|
return myPartitioningEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StoreMetaSourceInformationEnum {
|
public enum StoreMetaSourceInformationEnum {
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Su
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||||
ResourceTable entity = readEntityLatestVersion(theId);
|
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
return 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.data.IForcedIdDao;
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
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.cross.ResourcePersistentId;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.PartitionId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
@ -269,6 +271,9 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
||||||
return doSearch(theResourceName, theParams, null);
|
return doSearch(theResourceName, theParams, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RequestPartitionHelperService myRequestPartitionHelperService;
|
||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
@Override
|
@Override
|
||||||
public List<Suggestion> suggestKeywords(String theContext, String theSearchParam, String theText, RequestDetails theRequest) {
|
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) {
|
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
|
||||||
throw new InvalidRequestException("Invalid context: " + theContext);
|
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);
|
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.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
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.SearchRuntimeDetails;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
import ca.uhn.fhir.model.api.Include;
|
||||||
|
@ -37,12 +38,12 @@ import java.util.Set;
|
||||||
|
|
||||||
public interface ISearchBuilder {
|
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);
|
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);
|
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,
|
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.interceptor.JpaPreResourceAccessDetails;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
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.ResourceIndexedCompositeStringUnique;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
|
@ -165,6 +166,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private Integer myMaxResultsToFetch;
|
private Integer myMaxResultsToFetch;
|
||||||
private Set<ResourcePersistentId> myPidSet;
|
private Set<ResourcePersistentId> myPidSet;
|
||||||
private PredicateBuilder myPredicateBuilder;
|
private PredicateBuilder myPredicateBuilder;
|
||||||
|
private PartitionId myPartitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -181,7 +183,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) {
|
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) {
|
private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams, RequestDetails theRequest) {
|
||||||
|
@ -219,8 +221,8 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest) {
|
public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||||
init(theParams, theSearchUuid);
|
init(theParams, theSearchUuid, thePartitionId);
|
||||||
|
|
||||||
TypedQuery<Long> query = createQuery(null, null, true, theRequest);
|
TypedQuery<Long> query = createQuery(null, null, true, theRequest);
|
||||||
return new CountQueryIterator(query);
|
return new CountQueryIterator(query);
|
||||||
|
@ -235,8 +237,8 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) {
|
public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||||
init(theParams, theSearchRuntimeDetails.getSearchUuid());
|
init(theParams, theSearchRuntimeDetails.getSearchUuid(), thePartitionId);
|
||||||
|
|
||||||
if (myPidSet == null) {
|
if (myPidSet == null) {
|
||||||
myPidSet = new HashSet<>();
|
myPidSet = new HashSet<>();
|
||||||
|
@ -245,13 +247,16 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
return new QueryIterator(theSearchRuntimeDetails, theRequest);
|
return new QueryIterator(theSearchRuntimeDetails, theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init(SearchParameterMap theParams, String theSearchUuid) {
|
private void init(SearchParameterMap theParams, String theSearchUuid, PartitionId thePartitionId) {
|
||||||
myParams = theParams;
|
myParams = theParams;
|
||||||
myCriteriaBuilder = myEntityManager.getCriteriaBuilder();
|
myCriteriaBuilder = myEntityManager.getCriteriaBuilder();
|
||||||
mySearchUuid = theSearchUuid;
|
mySearchUuid = theSearchUuid;
|
||||||
myPredicateBuilder = new PredicateBuilder(this, myPredicateBuilderFactory);
|
myPredicateBuilder = new PredicateBuilder(this, myPredicateBuilderFactory);
|
||||||
|
myPartitionId = thePartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private TypedQuery<Long> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) {
|
private TypedQuery<Long> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) {
|
||||||
CriteriaQuery<Long> outerQuery;
|
CriteriaQuery<Long> outerQuery;
|
||||||
/*
|
/*
|
||||||
|
@ -296,7 +301,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
||||||
StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
|
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) {
|
if (myAlsoIncludePids == null) {
|
||||||
myAlsoIncludePids = new ArrayList<>(1);
|
myAlsoIncludePids = new ArrayList<>(1);
|
||||||
}
|
}
|
||||||
|
@ -352,6 +357,9 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
|
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
|
||||||
}
|
}
|
||||||
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
|
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
|
||||||
|
if (myPartitionId != null) {
|
||||||
|
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myPartitionIdValue"), myPartitionId.getPartitionId()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last updated
|
// 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")
|
@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);
|
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")
|
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
|
||||||
ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);
|
ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends BaseHapiFhirResourceDao<Su
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||||
ResourceTable entity = readEntityLatestVersion(theId);
|
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -70,7 +70,7 @@ public class DaoSearchParamSynchronizer {
|
||||||
theEntity.getParamsQuantity().remove(next);
|
theEntity.getParamsQuantity().remove(next);
|
||||||
}
|
}
|
||||||
for (T next : quantitiesToAdd) {
|
for (T next : quantitiesToAdd) {
|
||||||
next.setTenantId(theEntity.getTenantId());
|
next.setPartitionId(theEntity.getPartitionId());
|
||||||
myEntityManager.merge(next);
|
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.ResourceLookup;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
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.model.search.StorageProcessingMessage;
|
||||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
@ -51,6 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -59,6 +61,7 @@ import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -132,14 +135,14 @@ public class IdHelperService {
|
||||||
* @throws ResourceNotFoundException If the ID can not be found
|
* @throws ResourceNotFoundException If the ID can not be found
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public ResourcePersistentId resolveResourcePersistentIds(String theResourceType, String theId) {
|
public ResourcePersistentId resolveResourcePersistentIds(PartitionId thePartitionId, String theResourceType, String theId) {
|
||||||
Long retVal;
|
Long retVal;
|
||||||
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
|
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
|
||||||
if (myDaoConfig.isDeleteEnabled()) {
|
if (myDaoConfig.isDeleteEnabled()) {
|
||||||
retVal = resolveResourceIdentity(theResourceType, theId);
|
retVal = resolveResourceIdentity(thePartitionId, theResourceType, theId);
|
||||||
} else {
|
} else {
|
||||||
String key = theResourceType + "/" + theId;
|
String key = thePartitionId + "/" + theResourceType + "/" + theId;
|
||||||
retVal = myPersistentIdCache.get(key, t -> resolveResourceIdentity(theResourceType, theId));
|
retVal = myPersistentIdCache.get(key, t -> resolveResourceIdentity(thePartitionId, theResourceType, theId));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -248,12 +251,18 @@ public class IdHelperService {
|
||||||
return typeToIds;
|
return typeToIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long resolveResourceIdentity(String theResourceType, String theId) {
|
private Long resolveResourceIdentity(@Nullable PartitionId thePartitionId, @Nonnull String theResourceType, @Nonnull String theId) {
|
||||||
Long retVal;
|
Optional<Long> pid;
|
||||||
retVal = myForcedIdDao
|
if (thePartitionId != null) {
|
||||||
.findByTypeAndForcedId(theResourceType, theId)
|
pid = myForcedIdDao.findByPartitionIdAndTypeAndForcedId(thePartitionId.getPartitionId(), theResourceType, theId);
|
||||||
.orElseThrow(() -> new ResourceNotFoundException(new IdDt(theResourceType, theId)));
|
} else {
|
||||||
return retVal;
|
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) {
|
private Collection<IResourceLookup> translateForcedIdToPids(RequestDetails theRequest, Collection<IIdType> theId) {
|
||||||
|
@ -274,7 +283,7 @@ public class IdHelperService {
|
||||||
if (!pids.isEmpty()) {
|
if (!pids.isEmpty()) {
|
||||||
myResourceTableDao.findLookupFieldsByResourcePid(pids)
|
myResourceTableDao.findLookupFieldsByResourcePid(pids)
|
||||||
.stream()
|
.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);
|
.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.IDao;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilder;
|
import ca.uhn.fhir.jpa.dao.SearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
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.ResourceIndexedSearchParamDate;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
|
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
|
||||||
|
@ -125,8 +126,10 @@ abstract class BasePredicateBuilder {
|
||||||
myQueryRoot.addPredicate(myCriteriaBuilder.equal(hashPresence, hash));
|
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("myResourceType"), theResourceName));
|
||||||
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
|
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
|
||||||
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
|
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
|
||||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.PartitionId;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -31,5 +32,6 @@ public interface IPredicateBuilder {
|
||||||
Predicate addPredicate(String theResourceName,
|
Predicate addPredicate(String theResourceName,
|
||||||
String theParamName,
|
String theParamName,
|
||||||
List<? extends IQueryParameterType> theList,
|
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);
|
return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) {
|
public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||||
myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest);
|
myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, thePartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest) {
|
Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||||
return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest);
|
return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest, thePartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<ResourceTable, ResourceLink> theJoin) {
|
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<ResourceTable, ResourceLink> theJoin) {
|
||||||
return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin);
|
return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addPredicateResourceId(List<List<IQueryParameterType>> theAndOrParams, String theResourceName, RequestDetails theRequest) {
|
void addPredicateResourceId(List<List<IQueryParameterType>> theAndOrParams, String theResourceName, PartitionId thePartitionId) {
|
||||||
myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, theRequest);
|
myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, thePartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
|
public Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
|
||||||
return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequest);
|
return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, thePartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theStringJoin) {
|
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.SearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
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.ResourceHistoryProvenanceEntity;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
||||||
|
@ -98,7 +99,7 @@ import static org.apache.commons.lang3.StringUtils.trim;
|
||||||
@Scope("prototype")
|
@Scope("prototype")
|
||||||
class PredicateBuilderReference extends BasePredicateBuilder {
|
class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderReference.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderReference.class);
|
||||||
|
private final PredicateBuilder myPredicateBuilder;
|
||||||
@Autowired
|
@Autowired
|
||||||
IdHelperService myIdHelperService;
|
IdHelperService myIdHelperService;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -110,8 +111,6 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
|
||||||
private final PredicateBuilder myPredicateBuilder;
|
|
||||||
|
|
||||||
PredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
|
PredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
|
||||||
super(theSearchBuilder);
|
super(theSearchBuilder);
|
||||||
myPredicateBuilder = thePredicateBuilder;
|
myPredicateBuilder = thePredicateBuilder;
|
||||||
|
@ -125,7 +124,8 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
String theParamName,
|
String theParamName,
|
||||||
List<? extends IQueryParameterType> theList,
|
List<? extends IQueryParameterType> theList,
|
||||||
SearchFilterParser.CompareOperation operation,
|
SearchFilterParser.CompareOperation operation,
|
||||||
RequestDetails theRequest) {
|
RequestDetails theRequest,
|
||||||
|
PartitionId thePartitionId) {
|
||||||
|
|
||||||
//Is this just to ensure the chain has been split correctly???
|
//Is this just to ensure the chain has been split correctly???
|
||||||
assert theParamName.contains(".") == false;
|
assert theParamName.contains(".") == false;
|
||||||
|
@ -177,7 +177,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
* Handle chained search, e.g. Patient?organization.name=Kwik-e-mart
|
* 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
|
* This is for handling queries like the following: /Observation?device.identifier=urn:system|foo in which we use a chain
|
||||||
* on the device.
|
* 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;
|
final List<Class<? extends IBaseResource>> resourceTypes;
|
||||||
if (!theReferenceParam.hasResourceType()) {
|
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 pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin);
|
||||||
Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ);
|
Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ);
|
||||||
|
@ -410,9 +410,9 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
String message = new StringBuilder()
|
String message = new StringBuilder()
|
||||||
.append("This search uses an unqualified resource(a parameter in a chain without a resource type). ")
|
.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("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("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();
|
.toString();
|
||||||
StorageProcessingMessage msg = new StorageProcessingMessage()
|
StorageProcessingMessage msg = new StorageProcessingMessage()
|
||||||
.setMessage(message);
|
.setMessage(message);
|
||||||
|
@ -472,7 +472,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
return chainValue;
|
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);
|
Subquery<Long> subQ = myQueryRoot.subquery(Long.class);
|
||||||
/*
|
/*
|
||||||
* We're doing a chain call, so push the current query root
|
* 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")));
|
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
|
||||||
|
|
||||||
if (theFoundChainMatch) {
|
if (theFoundChainMatch) {
|
||||||
searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest);
|
searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest, thePartitionId);
|
||||||
subQ.where(myQueryRoot.getPredicateArray());
|
subQ.where(myQueryRoot.getPredicateArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,7 +501,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
return subQ;
|
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()) {
|
if (theAndOrParams.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -509,7 +509,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
|
|
||||||
switch (theParamName) {
|
switch (theParamName) {
|
||||||
case IAnyResource.SP_RES_ID:
|
case IAnyResource.SP_RES_ID:
|
||||||
myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, theRequest);
|
myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, thePartitionId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IAnyResource.SP_RES_LANGUAGE:
|
case IAnyResource.SP_RES_LANGUAGE:
|
||||||
|
@ -518,7 +518,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.PARAM_HAS:
|
case Constants.PARAM_HAS:
|
||||||
addPredicateHas(theResourceName, theAndOrParams, theRequest);
|
addPredicateHas(theResourceName, theAndOrParams, theRequest, thePartitionId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.PARAM_TAG:
|
case Constants.PARAM_TAG:
|
||||||
|
@ -548,7 +548,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
break;
|
break;
|
||||||
case REFERENCE:
|
case REFERENCE:
|
||||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||||
addPredicate(theResourceName, theParamName, nextAnd, null, theRequest);
|
addPredicate(theResourceName, theParamName, nextAnd, null, theRequest, thePartitionId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STRING:
|
case STRING:
|
||||||
|
@ -613,7 +613,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
// point to do something more fancy...
|
// point to do something more fancy...
|
||||||
ArrayList<Predicate> holdPredicates = new ArrayList<>(myQueryRoot.getPredicates());
|
ArrayList<Predicate> holdPredicates = new ArrayList<>(myQueryRoot.getPredicates());
|
||||||
|
|
||||||
Predicate filterPredicate = processFilter(filter, theResourceName, theRequest);
|
Predicate filterPredicate = processFilter(filter, theResourceName, theRequest, thePartitionId);
|
||||||
myQueryRoot.clearPredicates();
|
myQueryRoot.clearPredicates();
|
||||||
myQueryRoot.addPredicates(holdPredicates);
|
myQueryRoot.addPredicates(holdPredicates);
|
||||||
myQueryRoot.addPredicate(filterPredicate);
|
myQueryRoot.addPredicate(filterPredicate);
|
||||||
|
@ -629,20 +629,17 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate processFilter(SearchFilterParser.Filter theFilter,
|
private Predicate processFilter(SearchFilterParser.Filter theFilter, String theResourceName, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||||
String theResourceName, RequestDetails theRequest) {
|
|
||||||
|
|
||||||
if (theFilter instanceof SearchFilterParser.FilterParameter) {
|
if (theFilter instanceof SearchFilterParser.FilterParameter) {
|
||||||
return processFilterParameter((SearchFilterParser.FilterParameter) theFilter,
|
return processFilterParameter((SearchFilterParser.FilterParameter) theFilter,
|
||||||
theResourceName, theRequest);
|
theResourceName, theRequest, thePartitionId);
|
||||||
} else if (theFilter instanceof SearchFilterParser.FilterLogical) {
|
} else if (theFilter instanceof SearchFilterParser.FilterLogical) {
|
||||||
// Left side
|
// Left side
|
||||||
Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(),
|
Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(), theResourceName, theRequest, thePartitionId);
|
||||||
theResourceName, theRequest);
|
|
||||||
|
|
||||||
// Right side
|
// Right side
|
||||||
Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(),
|
Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(), theResourceName, theRequest, thePartitionId);
|
||||||
theResourceName, theRequest);
|
|
||||||
|
|
||||||
if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) {
|
if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) {
|
||||||
return myCriteriaBuilder.and(xPredicate, yPredicate);
|
return myCriteriaBuilder.and(xPredicate, yPredicate);
|
||||||
|
@ -650,14 +647,13 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
return myCriteriaBuilder.or(xPredicate, yPredicate);
|
return myCriteriaBuilder.or(xPredicate, yPredicate);
|
||||||
}
|
}
|
||||||
} else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) {
|
} else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) {
|
||||||
return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(),
|
return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(), theResourceName, theRequest, thePartitionId);
|
||||||
theResourceName, theRequest);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter,
|
private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter,
|
||||||
String theResourceName, RequestDetails theRequest) {
|
String theResourceName, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||||
|
|
||||||
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, theFilter.getParamPath().getName());
|
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, theFilter.getParamPath().getName());
|
||||||
|
|
||||||
|
@ -670,7 +666,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
theFilter.getValue());
|
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 {
|
} else {
|
||||||
throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search");
|
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 chain = (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() : null;
|
||||||
String value = theFilter.getValue();
|
String value = theFilter.getValue();
|
||||||
ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value);
|
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) {
|
} else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) {
|
||||||
return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation());
|
return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation());
|
||||||
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
|
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
|
||||||
|
@ -794,7 +790,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate predicate = null;
|
Predicate predicate;
|
||||||
if ((operation == null) ||
|
if ((operation == null) ||
|
||||||
(operation == SearchFilterParser.CompareOperation.eq)) {
|
(operation == SearchFilterParser.CompareOperation.eq)) {
|
||||||
predicate = myQueryRoot.get("myLanguage").as(String.class).in(values);
|
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) {
|
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) {
|
if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) {
|
||||||
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled");
|
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled");
|
||||||
throw new InvalidRequestException(msg);
|
throw new InvalidRequestException(msg);
|
||||||
|
@ -848,7 +847,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
return retVal;
|
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) {
|
for (List<? extends IQueryParameterType> nextOrList : theHasParameters) {
|
||||||
|
|
||||||
|
@ -878,8 +877,6 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
throw new InvalidRequestException("Invalid resource type: " + targetResourceType);
|
throw new InvalidRequestException("Invalid resource type: " + targetResourceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert parameterName != null;
|
|
||||||
|
|
||||||
//Ensure that the name of the search param
|
//Ensure that the name of the search param
|
||||||
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
|
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
|
||||||
// exists on the target resource type.
|
// exists on the target resource type.
|
||||||
|
@ -909,11 +906,11 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
||||||
String chainedPartOfParameter = getChainedPart(parameterName);
|
String chainedPartOfParameter = getChainedPart(parameterName);
|
||||||
orValues.stream()
|
orValues.stream()
|
||||||
.filter(qp -> qp instanceof ReferenceParam)
|
.filter(qp -> qp instanceof ReferenceParam)
|
||||||
.map(qp -> (ReferenceParam)qp)
|
.map(qp -> (ReferenceParam) qp)
|
||||||
.forEach(rp -> rp.setChain(getChainedPart(chainedPartOfParameter)));
|
.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);
|
Join<ResourceTable, ResourceLink> join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
|
||||||
|
|
||||||
Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join);
|
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.SearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
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.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -58,9 +58,9 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@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) {
|
if (nextPredicate != null) {
|
||||||
myQueryRoot.addPredicate(nextPredicate);
|
myQueryRoot.addPredicate(nextPredicate);
|
||||||
|
@ -71,7 +71,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@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;
|
Predicate nextPredicate = null;
|
||||||
|
|
||||||
Set<ResourcePersistentId> allOrPids = null;
|
Set<ResourcePersistentId> allOrPids = null;
|
||||||
|
@ -89,7 +89,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
|
||||||
if (isNotBlank(value)) {
|
if (isNotBlank(value)) {
|
||||||
haveValue = true;
|
haveValue = true;
|
||||||
try {
|
try {
|
||||||
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theResourceName, valueAsId.getIdPart());
|
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(thePartitionId, theResourceName, valueAsId.getIdPart());
|
||||||
orPids.add(pid);
|
orPids.add(pid);
|
||||||
} catch (ResourceNotFoundException e) {
|
} catch (ResourceNotFoundException e) {
|
||||||
// This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest
|
// 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.SearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
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.ResourceIndexedSearchParamUri;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
@ -55,16 +56,22 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
|
||||||
public Predicate addPredicate(String theResourceName,
|
public Predicate addPredicate(String theResourceName,
|
||||||
String theParamName,
|
String theParamName,
|
||||||
List<? extends IQueryParameterType> theList,
|
List<? extends IQueryParameterType> theList,
|
||||||
SearchFilterParser.CompareOperation operation) {
|
SearchFilterParser.CompareOperation operation,
|
||||||
|
PartitionId thePartitionId) {
|
||||||
|
|
||||||
Join<ResourceTable, ResourceIndexedSearchParamUri> join = createJoin(SearchBuilderJoinEnum.URI, theParamName);
|
Join<ResourceTable, ResourceIndexedSearchParamUri> join = createJoin(SearchBuilderJoinEnum.URI, theParamName);
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
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) {
|
for (IQueryParameterType nextOr : theList) {
|
||||||
|
|
||||||
if (nextOr instanceof UriParam) {
|
if (nextOr instanceof UriParam) {
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||||
ResourceTable entity = readEntityLatestVersion(theId);
|
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||||
ResourceTable entity = readEntityLatestVersion(theId);
|
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -37,7 +37,7 @@ public interface ISearchCoordinatorSvc {
|
||||||
|
|
||||||
List<ResourcePersistentId> getResources(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails);
|
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
|
* 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.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.dao.*;
|
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.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
|
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
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.SearchRuntimeDetails;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||||
|
@ -246,7 +248,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
String resourceType = search.getResourceType();
|
String resourceType = search.getResourceType();
|
||||||
SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search"));
|
SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search"));
|
||||||
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(resourceType);
|
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);
|
myIdToSearchTask.put(search.getUuid(), task);
|
||||||
myExecutor.submit(task);
|
myExecutor.submit(task);
|
||||||
}
|
}
|
||||||
|
@ -284,8 +287,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
theRetVal.setInterceptorBroadcaster(myInterceptorBroadcaster);
|
theRetVal.setInterceptorBroadcaster(myInterceptorBroadcaster);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RequestPartitionHelperService myRequestPartitionHelperService;
|
||||||
|
|
||||||
@Override
|
@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();
|
final String searchUuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
ourLog.debug("Registering new search {}", searchUuid);
|
ourLog.debug("Registering new search {}", searchUuid);
|
||||||
|
@ -298,7 +304,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
|
|
||||||
if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null) {
|
if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null) {
|
||||||
ourLog.debug("Search {} is loading in synchronous mode", searchUuid);
|
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);
|
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
|
||||||
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
|
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);
|
myIdToSearchTask.put(search.getUuid(), task);
|
||||||
myExecutor.submit(task);
|
myExecutor.submit(task);
|
||||||
|
|
||||||
|
@ -380,7 +388,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@org.jetbrains.annotations.Nullable
|
@Nullable
|
||||||
private IBundleProvider findCachedQuery(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString) {
|
private IBundleProvider findCachedQuery(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString) {
|
||||||
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
|
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
|
||||||
PersistedJpaBundleProvider foundSearchProvider = txTemplate.execute(t -> {
|
PersistedJpaBundleProvider foundSearchProvider = txTemplate.execute(t -> {
|
||||||
|
@ -416,10 +424,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
return retVal;
|
return retVal;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (foundSearchProvider != null) {
|
// May be null
|
||||||
return foundSearchProvider;
|
return foundSearchProvider;
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -441,7 +447,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
return searchToUse;
|
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 searchRuntimeDetails = new SearchRuntimeDetails(theRequestDetails, theSearchUuid);
|
||||||
searchRuntimeDetails.setLoadSynchronous(true);
|
searchRuntimeDetails.setLoadSynchronous(true);
|
||||||
|
|
||||||
|
@ -453,7 +459,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
// Load the results synchronously
|
// Load the results synchronously
|
||||||
final List<ResourcePersistentId> pids = new ArrayList<>();
|
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()) {
|
while (resultIter.hasNext()) {
|
||||||
pids.add(resultIter.next());
|
pids.add(resultIter.next());
|
||||||
if (theLoadSynchronousUpTo != null && pids.size() >= theLoadSynchronousUpTo) {
|
if (theLoadSynchronousUpTo != null && pids.size() >= theLoadSynchronousUpTo) {
|
||||||
|
@ -595,6 +602,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
private final CountDownLatch myCompletionLatch;
|
private final CountDownLatch myCompletionLatch;
|
||||||
private final ArrayList<ResourcePersistentId> myUnsyncedPids = new ArrayList<>();
|
private final ArrayList<ResourcePersistentId> myUnsyncedPids = new ArrayList<>();
|
||||||
private final RequestDetails myRequest;
|
private final RequestDetails myRequest;
|
||||||
|
private final PartitionId myPartitionId;
|
||||||
private Search mySearch;
|
private Search mySearch;
|
||||||
private boolean myAbortRequested;
|
private boolean myAbortRequested;
|
||||||
private int myCountSavedTotal = 0;
|
private int myCountSavedTotal = 0;
|
||||||
|
@ -605,10 +613,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
private Integer myMaxResultsToFetch;
|
private Integer myMaxResultsToFetch;
|
||||||
private SearchRuntimeDetails mySearchRuntimeDetails;
|
private SearchRuntimeDetails mySearchRuntimeDetails;
|
||||||
private Transaction myParentTransaction;
|
private Transaction myParentTransaction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* 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;
|
mySearch = theSearch;
|
||||||
myCallingDao = theCallingDao;
|
myCallingDao = theCallingDao;
|
||||||
myParams = theParams;
|
myParams = theParams;
|
||||||
|
@ -616,6 +625,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
myCompletionLatch = new CountDownLatch(1);
|
myCompletionLatch = new CountDownLatch(1);
|
||||||
mySearchRuntimeDetails = new SearchRuntimeDetails(theRequest, mySearch.getUuid());
|
mySearchRuntimeDetails = new SearchRuntimeDetails(theRequest, mySearch.getUuid());
|
||||||
mySearchRuntimeDetails.setQueryString(theParams.toNormalizedQueryString(theCallingDao.getContext()));
|
mySearchRuntimeDetails.setQueryString(theParams.toNormalizedQueryString(theCallingDao.getContext()));
|
||||||
|
myPartitionId = thePartitionId;
|
||||||
myRequest = theRequest;
|
myRequest = theRequest;
|
||||||
myParentTransaction = ElasticApm.currentTransaction();
|
myParentTransaction = ElasticApm.currentTransaction();
|
||||||
}
|
}
|
||||||
|
@ -977,7 +987,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
if (wantCount) {
|
if (wantCount) {
|
||||||
ourLog.trace("Performing count");
|
ourLog.trace("Performing count");
|
||||||
ISearchBuilder sb = newSearchBuilder();
|
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();
|
Long count = countIterator.next();
|
||||||
ourLog.trace("Got count {}", count);
|
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
|
* 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);
|
assert (resultIterator != null);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1105,8 +1115,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
|
|
||||||
public class SearchContinuationTask extends SearchTask {
|
public class SearchContinuationTask extends SearchTask {
|
||||||
|
|
||||||
public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest) {
|
public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, PartitionId thePartitionId) {
|
||||||
super(theSearch, theCallingDao, theParams, theResourceType, theRequest);
|
super(theSearch, theCallingDao, theParams, theResourceType, theRequest, thePartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
|
||||||
present.setResource(theResource);
|
present.setResource(theResource);
|
||||||
present.setParamName(paramName);
|
present.setParamName(paramName);
|
||||||
present.setPresent(next.getValue());
|
present.setPresent(next.getValue());
|
||||||
present.setTenantId(theResource.getTenantId());
|
present.setPartitionId(theResource.getPartitionId());
|
||||||
present.calculateHashes();
|
present.calculateHashes();
|
||||||
|
|
||||||
newHashToPresence.put(present.getHashPresence(), present);
|
newHashToPresence.put(present.getHashPresence(), present);
|
||||||
|
|
|
@ -117,7 +117,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) {
|
public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) {
|
||||||
return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart());
|
return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@ -291,7 +291,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
||||||
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
|
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
|
||||||
|
|
||||||
IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource);
|
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());
|
ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong());
|
||||||
|
|
||||||
ourLog.info("CodeSystem resource has ID: {}", csId.getValue());
|
ourLog.info("CodeSystem resource has ID: {}", csId.getValue());
|
||||||
|
@ -551,7 +551,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) {
|
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) {
|
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {
|
||||||
|
|
|
@ -280,6 +280,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IResourceTagDao myResourceTagDao;
|
protected IResourceTagDao myResourceTagDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected IResourceHistoryTagDao myResourceHistoryTagDao;
|
||||||
|
@Autowired
|
||||||
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
protected IFulltextSearchSvc mySearchDao;
|
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.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
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.model.search.SearchStatusEnum;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
|
||||||
|
@ -147,7 +148,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
List<ResourcePersistentId> pids = createPidSequence(800);
|
List<ResourcePersistentId> pids = createPidSequence(800);
|
||||||
IResultIterator iter = new FailAfterNIterator(new SlowIterator(pids.iterator(), 2), 300);
|
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);
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null);
|
||||||
assertNotNull(result.getUuid());
|
assertNotNull(result.getUuid());
|
||||||
|
@ -180,7 +181,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
List<ResourcePersistentId> pids = createPidSequence(800);
|
List<ResourcePersistentId> pids = createPidSequence(800);
|
||||||
SlowIterator iter = new SlowIterator(pids.iterator(), 1);
|
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());
|
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
|
||||||
|
|
||||||
when(mySearchCacheSvc.save(any())).thenAnswer(t -> {
|
when(mySearchCacheSvc.save(any())).thenAnswer(t -> {
|
||||||
|
@ -262,7 +263,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
List<ResourcePersistentId> pids = createPidSequence(800);
|
List<ResourcePersistentId> pids = createPidSequence(800);
|
||||||
SlowIterator iter = new SlowIterator(pids.iterator(), 2);
|
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());
|
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);
|
List<ResourcePersistentId> pids = createPidSequence(800);
|
||||||
SlowIterator iter = new SlowIterator(pids.iterator(), 500);
|
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);
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null);
|
||||||
assertNotNull(result.getUuid());
|
assertNotNull(result.getUuid());
|
||||||
|
@ -331,7 +332,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
List<ResourcePersistentId> pids = createPidSequence(800);
|
List<ResourcePersistentId> pids = createPidSequence(800);
|
||||||
IResultIterator iter = new SlowIterator(pids.iterator(), 2);
|
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 ->{
|
when(mySearchCacheSvc.save(any())).thenAnswer(t ->{
|
||||||
ourLog.info("Saving search");
|
ourLog.info("Saving search");
|
||||||
return t.getArgument( 0, Search.class);
|
return t.getArgument( 0, Search.class);
|
||||||
|
@ -379,7 +380,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
List<ResourcePersistentId> pids = createPidSequence(100);
|
List<ResourcePersistentId> pids = createPidSequence(100);
|
||||||
SlowIterator iter = new SlowIterator(pids.iterator(), 2);
|
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());
|
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"));
|
params.add("name", new StringParam("ANAME"));
|
||||||
|
|
||||||
List<ResourcePersistentId> pids = createPidSequence(800);
|
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());
|
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"));
|
params.add("name", new StringParam("ANAME"));
|
||||||
|
|
||||||
List<ResourcePersistentId> pids = createPidSequence(800);
|
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);
|
pids = createPidSequence(110);
|
||||||
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(eq(pids), any(Collection.class), any(List.class), anyBoolean(), nullable(RequestDetails.class));
|
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;
|
private Date myUpdated;
|
||||||
|
|
||||||
@Embedded
|
@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
|
* 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
|
@Transient
|
||||||
private transient String myTransientForcedId;
|
private transient String myTransientForcedId;
|
||||||
|
|
||||||
public TenantId getTenantId() {
|
public PartitionId getPartitionId() {
|
||||||
return myTenantId;
|
return myPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTenantId(TenantId theTenantId) {
|
public void setPartitionId(PartitionId thePartitionId) {
|
||||||
myTenantId = theTenantId;
|
myPartitionId = thePartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTransientForcedId() {
|
public String getTransientForcedId() {
|
||||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Embedded;
|
import javax.persistence.Embedded;
|
||||||
import javax.persistence.MappedSuperclass;
|
import javax.persistence.MappedSuperclass;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
@ -28,14 +29,21 @@ import java.io.Serializable;
|
||||||
public abstract class BaseResourceIndex implements Serializable {
|
public abstract class BaseResourceIndex implements Serializable {
|
||||||
|
|
||||||
@Embedded
|
@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) {
|
public void setPartitionId(PartitionId thePartitionId) {
|
||||||
myTenantId = theTenantId;
|
myPartitionId = thePartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Long getId();
|
public abstract Long getId();
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Embedded;
|
||||||
import javax.persistence.JoinColumn;
|
import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.MappedSuperclass;
|
import javax.persistence.MappedSuperclass;
|
||||||
|
@ -38,6 +39,9 @@ public class BaseTag implements Serializable {
|
||||||
@Column(name = "TAG_ID", insertable = false, updatable = false)
|
@Column(name = "TAG_ID", insertable = false, updatable = false)
|
||||||
private Long myTagId;
|
private Long myTagId;
|
||||||
|
|
||||||
|
@Embedded
|
||||||
|
private PartitionId myPartitionId;
|
||||||
|
|
||||||
public Long getTagId() {
|
public Long getTagId() {
|
||||||
return myTagId;
|
return myTagId;
|
||||||
}
|
}
|
||||||
|
@ -50,4 +54,12 @@ public class BaseTag implements Serializable {
|
||||||
myTag = theTag;
|
myTag = theTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PartitionId getPartitionId() {
|
||||||
|
return myPartitionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPartitionId(PartitionId thePartitionId) {
|
||||||
|
myPartitionId = thePartitionId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,9 @@ public class ForcedId {
|
||||||
@ColumnDefault("''")
|
@ColumnDefault("''")
|
||||||
@Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true)
|
@Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true)
|
||||||
private String myResourceType;
|
private String myResourceType;
|
||||||
|
|
||||||
@Embedded
|
@Embedded
|
||||||
private TenantId myTenantId;
|
private PartitionId myPartitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -96,11 +97,11 @@ public class ForcedId {
|
||||||
return myId;
|
return myId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TenantId getTenantId() {
|
public PartitionId getPartitionId() {
|
||||||
return myTenantId;
|
return myPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTenantId(TenantId theTenantId) {
|
public void setPartitionId(PartitionId thePartitionId) {
|
||||||
myTenantId = theTenantId;
|
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;
|
private String myRequestId;
|
||||||
// FIXME: make sure this gets populated
|
// FIXME: make sure this gets populated
|
||||||
@Embedded
|
@Embedded
|
||||||
private TenantId myTenantId;
|
private PartitionId myPartitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -86,11 +86,11 @@ public class ResourceHistoryProvenanceEntity {
|
||||||
return myId;
|
return myId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TenantId getTenantId() {
|
public PartitionId getPartitionId() {
|
||||||
return myTenantId;
|
return myPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTenantId(TenantId theTenantId) {
|
public void setPartitionId(PartitionId thePartitionId) {
|
||||||
myTenantId = theTenantId;
|
myPartitionId = thePartitionId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addTag(ResourceTag theTag) {
|
public void addTag(ResourceTag theTag) {
|
||||||
ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag());
|
ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag(), getPartitionId());
|
||||||
tag.setResourceType(theTag.getResourceType());
|
tag.setResourceType(theTag.getResourceType());
|
||||||
getTags().add(tag);
|
getTags().add(tag);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag);
|
ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag, getPartitionId());
|
||||||
getTags().add(historyTag);
|
getTags().add(historyTag);
|
||||||
return 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);
|
setTag(theTag);
|
||||||
setResource(theResourceHistoryTable);
|
setResource(theResourceHistoryTable);
|
||||||
setResourceId(theResourceHistoryTable.getResourceId());
|
setResourceId(theResourceHistoryTable.getResourceId());
|
||||||
setResourceType(theResourceHistoryTable.getResourceType());
|
setResourceType(theResourceHistoryTable.getResourceType());
|
||||||
|
setPartitionId(thePartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceHistoryTable getResourceHistory() {
|
public ResourceHistoryTable getResourceHistory() {
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
||||||
@Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH)
|
@Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH)
|
||||||
private String myIndexString;
|
private String myIndexString;
|
||||||
@Embedded
|
@Embedded
|
||||||
private TenantId myTenantId;
|
private PartitionId myPartitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -64,15 +64,15 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
||||||
public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) {
|
public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) {
|
||||||
setResource(theResource);
|
setResource(theResource);
|
||||||
setIndexString(theIndexString);
|
setIndexString(theIndexString);
|
||||||
setTenantId(theResource.getTenantId());
|
setPartitionId(theResource.getPartitionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TenantId getTenantId() {
|
public PartitionId getPartitionId() {
|
||||||
return myTenantId;
|
return myPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTenantId(TenantId theTenantId) {
|
public void setPartitionId(PartitionId thePartitionId) {
|
||||||
myTenantId = theTenantId;
|
myPartitionId = thePartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -127,7 +127,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
||||||
.append("id", myId)
|
.append("id", myId)
|
||||||
.append("resourceId", myResourceId)
|
.append("resourceId", myResourceId)
|
||||||
.append("indexString", myIndexString)
|
.append("indexString", myIndexString)
|
||||||
.append("tenant", myTenantId)
|
.append("tenant", myPartitionId)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,7 +242,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResourceTag tag = new ResourceTag(this, theTag);
|
ResourceTag tag = new ResourceTag(this, theTag, getPartitionId());
|
||||||
getTags().add(tag);
|
getTags().add(tag);
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
@ -556,7 +556,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
retVal.setDeleted(getDeleted());
|
retVal.setDeleted(getDeleted());
|
||||||
retVal.setResourceTable(this);
|
retVal.setResourceTable(this);
|
||||||
retVal.setForcedId(getForcedId());
|
retVal.setForcedId(getForcedId());
|
||||||
retVal.setTenantId(getTenantId());
|
retVal.setPartitionId(getPartitionId());
|
||||||
|
|
||||||
retVal.getTags().clear();
|
retVal.getTags().clear();
|
||||||
|
|
||||||
|
|
|
@ -51,21 +51,15 @@ public class ResourceTag extends BaseTag {
|
||||||
@Column(name = "RES_ID", insertable = false, updatable = false)
|
@Column(name = "RES_ID", insertable = false, updatable = false)
|
||||||
private Long myResourceId;
|
private Long myResourceId;
|
||||||
|
|
||||||
@Embedded
|
|
||||||
private TenantId myTenantId;
|
|
||||||
|
|
||||||
public ResourceTag() {
|
public ResourceTag() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag) {
|
public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag, PartitionId thePartitionId) {
|
||||||
setTag(theTag);
|
setTag(theTag);
|
||||||
setResource(theResourceTable);
|
setResource(theResourceTable);
|
||||||
setResourceId(theResourceTable.getId());
|
setResourceId(theResourceTable.getId());
|
||||||
setResourceType(theResourceTable.getResourceType());
|
setResourceType(theResourceTable.getResourceType());
|
||||||
}
|
setPartitionId(thePartitionId);
|
||||||
|
|
||||||
public void setTenantId(TenantId theTenantId) {
|
|
||||||
myTenantId = theTenantId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getResourceId() {
|
public Long getResourceId() {
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class SearchParamPresent implements Serializable {
|
||||||
@Column(name = "HASH_PRESENCE")
|
@Column(name = "HASH_PRESENCE")
|
||||||
private Long myHashPresence;
|
private Long myHashPresence;
|
||||||
@Embedded
|
@Embedded
|
||||||
private TenantId myTenantId;
|
private PartitionId myPartitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -112,16 +112,16 @@ public class SearchParamPresent implements Serializable {
|
||||||
b.append("resPid", myResource.getIdDt().toUnqualifiedVersionless().getValue());
|
b.append("resPid", myResource.getIdDt().toUnqualifiedVersionless().getValue());
|
||||||
b.append("paramName", myParamName);
|
b.append("paramName", myParamName);
|
||||||
b.append("present", myPresent);
|
b.append("present", myPresent);
|
||||||
b.append("tenant", myTenantId);
|
b.append("tenant", myPartitionId);
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TenantId getTenantId() {
|
public PartitionId getPartitionId() {
|
||||||
return myTenantId;
|
return myPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTenantId(TenantId theTenantId) {
|
public void setPartitionId(PartitionId thePartitionId) {
|
||||||
myTenantId = theTenantId;
|
myPartitionId = thePartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long calculateHashPresence(String theResourceType, String theParamName, Boolean thePresent) {
|
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>-->
|
<!--<derby_version>10.15.1.3</derby_version>-->
|
||||||
<error_prone_annotations_version>2.3.4</error_prone_annotations_version>
|
<error_prone_annotations_version>2.3.4</error_prone_annotations_version>
|
||||||
<error_prone_core_version>2.3.3</error_prone_core_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>
|
<gson_version>2.8.5</gson_version>
|
||||||
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
||||||
<jaxb_api_version>2.3.1</jaxb_api_version>
|
<jaxb_api_version>2.3.1</jaxb_api_version>
|
||||||
|
|
Loading…
Reference in New Issue