Multitenancy work

This commit is contained in:
jamesagnew 2020-04-12 20:47:32 -04:00
parent e55ccf88fc
commit c26a5553e9
105 changed files with 1936 additions and 985 deletions

View File

@ -65,12 +65,16 @@ public enum Pointcut {
* <li>
* ca.uhn.fhir.rest.client.api.IHttpRequest - The details of the request
* </li>
* <li>
* ca.uhn.fhir.rest.client.api.IRestfulClient - The client object making the request
* </li>
* </ul>
* </p>
* Hook methods must return <code>void</code>.
*/
CLIENT_REQUEST(void.class,
"ca.uhn.fhir.rest.client.api.IHttpRequest"
"ca.uhn.fhir.rest.client.api.IHttpRequest",
"ca.uhn.fhir.rest.client.api.IRestfulClient"
),
/**
@ -82,7 +86,12 @@ public enum Pointcut {
* <ul>
* <li>
* ca.uhn.fhir.rest.client.api.IHttpRequest - The details of the request
* ca.uhn.fhir.rest.client.api.IHttpRequest - The details of the response
* </li>
* <li>
* ca.uhn.fhir.rest.client.api.IHttpResponse - The details of the response
* </li>
* <li>
* ca.uhn.fhir.rest.client.api.IRestfulClient - The client object making the request
* </li>
* </ul>
* </p>
@ -90,7 +99,8 @@ public enum Pointcut {
*/
CLIENT_RESPONSE(void.class,
"ca.uhn.fhir.rest.client.api.IHttpRequest",
"ca.uhn.fhir.rest.client.api.IHttpResponse"
"ca.uhn.fhir.rest.client.api.IHttpResponse",
"ca.uhn.fhir.rest.client.api.IRestfulClient"
),
/**

View File

@ -63,6 +63,11 @@ public interface IHttpRequest {
*/
String getUri();
/**
* Modify the request URI, or null
*/
void setUri(String theUrl);
/**
* Return the HTTP verb (e.g. "GET")
*/

View File

@ -22,16 +22,17 @@ package ca.uhn.fhir.rest.param;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.CoverageIgnore;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.math.BigDecimal;
import static ca.uhn.fhir.model.primitive.IdDt.isValidLong;
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ {
@ -78,6 +79,17 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/
setValueAsQueryToken(null, null, qualifier, theValue);
}
/**
* Constructor
*
* @since 5.0.0
*/
public ReferenceParam(IIdType theValue) {
if (theValue != null) {
setValueAsQueryToken(null, null, null, theValue.getValue());
}
}
@Override
String doGetQueryParameterQualifier() {
StringBuilder b = new StringBuilder();
@ -209,7 +221,7 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/
*/
public ReferenceParam setValue(String theValue) {
IdDt id = new IdDt(theValue);
String qualifier= null;
String qualifier = null;
if (id.hasResourceType()) {
qualifier = ":" + id.getResourceType();
}

View File

@ -83,6 +83,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionContainsMultipleWithDuplica
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry({1}).request.method. Found value: "{0}"
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionMissingUrl=Unable to perform {0}, no URL provided.
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.noSystemOrTypeHistoryForPartitionAwareServer=Type- and Server- level history operation not supported on partitioned server
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.cantValidateWithNoResource=No resource supplied for $validate operation (resource is required unless mode is \"delete\")
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.deleteBlockedBecauseDisabled=Resource deletion is not permitted on this server
@ -155,5 +156,5 @@ ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantCreateDuplicatePartitionNam
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantDeleteDefaultPartition=Can not delete default partition
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantRenameDefaultPartition=Can not rename default partition
ca.uhn.fhir.jpa.partition.RequestTenantSelectingInterceptor.unknownTenantName=Unknown tenant: {0}
ca.uhn.fhir.jpa.partition.RequestTenantPartitionInterceptor.unknownTenantName=Unknown tenant: {0}

View File

@ -89,7 +89,12 @@ public class OkHttpRestfulRequest implements IHttpRequest {
return myUrl;
}
@Override
@Override
public void setUri(String theUrl) {
myUrl = theUrl;
}
@Override
public String getHttpVerbName() {
return myRequestTypeEnum.name();
}

View File

@ -34,6 +34,7 @@ import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.*;
@ -110,6 +111,11 @@ public class ApacheHttpRequest implements IHttpRequest {
return null;
}
@Override
public void setUri(String theUrl) {
myRequest.setURI(URI.create(theUrl));
}
@Override
public String getUri() {
return myRequest.getURI().toString();

View File

@ -297,6 +297,7 @@ public abstract class BaseClient implements IRestfulClient {
HookParams requestParams = new HookParams();
requestParams.add(IHttpRequest.class, httpRequest);
requestParams.add(IRestfulClient.class, this);
getInterceptorService().callHooks(Pointcut.CLIENT_REQUEST, requestParams);
response = httpRequest.execute();
@ -304,6 +305,7 @@ public abstract class BaseClient implements IRestfulClient {
HookParams responseParams = new HookParams();
responseParams.add(IHttpRequest.class, httpRequest);
responseParams.add(IHttpResponse.class, response);
responseParams.add(IRestfulClient.class, this);
getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams);
String mimeType;

View File

@ -20,7 +20,9 @@ package ca.uhn.fhir.rest.client.interceptor;
* #L%
*/
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
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.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -35,7 +37,8 @@ import java.io.IOException;
*
* @see ThreadLocalCapturingInterceptor for an interceptor that uses a ThreadLocal in order to work in multithreaded environments
*/
public class CapturingInterceptor implements IClientInterceptor {
@Interceptor
public class CapturingInterceptor {
private IHttpRequest myLastRequest;
private IHttpResponse myLastResponse;
@ -56,12 +59,12 @@ public class CapturingInterceptor implements IClientInterceptor {
return myLastResponse;
}
@Override
@Hook(value = Pointcut.CLIENT_REQUEST, order = InterceptorOrders.CAPTURING_INTERCEPTOR_REQUEST)
public void interceptRequest(IHttpRequest theRequest) {
myLastRequest = theRequest;
}
@Override
@Hook(value = Pointcut.CLIENT_RESPONSE, order = InterceptorOrders.CAPTURING_INTERCEPTOR_RESPONSE)
public void interceptResponse(IHttpResponse theResponse) {
//Buffer the reponse to avoid errors when content has already been read and the entity is not repeatable
bufferResponse(theResponse);

View File

@ -0,0 +1,11 @@
package ca.uhn.fhir.rest.client.interceptor;
public interface InterceptorOrders {
int LOGGING_INTERCEPTOR_REQUEST = -2;
int URL_TENANT_SELECTION_INTERCEPTOR_REQUEST = 100;
int CAPTURING_INTERCEPTOR_REQUEST = 1000;
int CAPTURING_INTERCEPTOR_RESPONSE = -1;
int LOGGING_INTERCEPTOR_RESPONSE = 1001;
}

View File

@ -20,6 +20,19 @@ package ca.uhn.fhir.rest.client.interceptor;
* #L%
*/
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.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@ -27,20 +40,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@Interceptor
public class LoggingInterceptor implements IClientInterceptor {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class);
@ -63,8 +62,7 @@ public class LoggingInterceptor implements IClientInterceptor {
/**
* Constructor for client logging interceptor
*
* @param theVerbose
* If set to true, all logging is enabled
* @param theVerbose If set to true, all logging is enabled
*/
public LoggingInterceptor(boolean theVerbose) {
if (theVerbose) {
@ -78,7 +76,7 @@ public class LoggingInterceptor implements IClientInterceptor {
}
@Override
@Hook(Pointcut.CLIENT_REQUEST)
@Hook(value = Pointcut.CLIENT_REQUEST, order = InterceptorOrders.LOGGING_INTERCEPTOR_RESPONSE)
public void interceptRequest(IHttpRequest theRequest) {
if (myLogRequestSummary) {
myLog.info("Client request: {}", theRequest);
@ -102,7 +100,7 @@ public class LoggingInterceptor implements IClientInterceptor {
}
@Override
@Hook(Pointcut.CLIENT_RESPONSE)
@Hook(value = Pointcut.CLIENT_RESPONSE, order = InterceptorOrders.LOGGING_INTERCEPTOR_REQUEST)
public void interceptResponse(IHttpResponse theResponse) throws IOException {
if (myLogResponseSummary) {
String message = "HTTP " + theResponse.getStatus() + " " + theResponse.getStatusInfo();
@ -167,18 +165,18 @@ public class LoggingInterceptor implements IClientInterceptor {
StringBuilder b = new StringBuilder();
if (theHeaders != null && !theHeaders.isEmpty()) {
Iterator<String> nameEntries = theHeaders.keySet().iterator();
while(nameEntries.hasNext()) {
while (nameEntries.hasNext()) {
String key = nameEntries.next();
Iterator<String> values = theHeaders.get(key).iterator();
while(values.hasNext()) {
while (values.hasNext()) {
String value = values.next();
b.append(key);
b.append(": ");
b.append(value);
if (nameEntries.hasNext() || values.hasNext()) {
b.append('\n');
}
b.append(key);
b.append(": ");
b.append(value);
if (nameEntries.hasNext() || values.hasNext()) {
b.append('\n');
}
}
}
}
return b;
@ -188,8 +186,7 @@ public class LoggingInterceptor implements IClientInterceptor {
* Sets a logger to use to log messages (default is a logger with this class' name). This can be used to redirect
* logs to a differently named logger instead.
*
* @param theLogger
* The logger to use. Must not be null.
* @param theLogger The logger to use. Must not be null.
*/
public void setLogger(Logger theLogger) {
Validate.notNull(theLogger, "theLogger can not be null");

View File

@ -0,0 +1,64 @@
package ca.uhn.fhir.rest.client.interceptor;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import org.apache.commons.lang3.Validate;
import static org.apache.commons.lang3.StringUtils.isBlank;
/**
* This interceptor adds a path element representing the tenant ID to each client request. It is primarily
* intended to be used with clients that are accessing servers using
* <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/multitenancy.html#url-base-multitenancy">URL Base Multitenancy</a>.
*/
public class UrlTenantSelectionInterceptor {
private String myTenantId;
/**
* Constructor
*/
public UrlTenantSelectionInterceptor() {
this(null);
}
/**
* Constructor
*
* @param theTenantId The tenant ID to add to URL base
*/
public UrlTenantSelectionInterceptor(String theTenantId) {
myTenantId = theTenantId;
}
/**
* Returns the tenant ID
*/
public String getTenantId() {
return myTenantId;
}
/**
* Sets the tenant ID
*/
public void setTenantId(String theTenantId) {
myTenantId = theTenantId;
}
@Hook(value = Pointcut.CLIENT_REQUEST, order = InterceptorOrders.URL_TENANT_SELECTION_INTERCEPTOR_REQUEST)
public void request(IRestfulClient theClient, IHttpRequest theRequest) {
String tenantId = getTenantId();
if (isBlank(tenantId)) {
return;
}
String requestUri = theRequest.getUri();
String serverBase = theClient.getServerBase();
Validate.isTrue(requestUri.startsWith(serverBase), "Request URI %s does not start with server base %s", requestUri, serverBase);
String newUri = serverBase + "/" + tenantId + requestUri.substring(serverBase.length());
theRequest.setUri(newUri);
}
}

View File

@ -0,0 +1,6 @@
---
type: add
issue: 1783
title: "The client interceptor pointcuts `CLIENT_REQUEST` and `CLIENT_RESPONSE` now allow a parameter of type `IRestfulClient` to be
injected, containing a reference to the client making the request. In addition, CLIENT_REQUEST interceptors are now able to
modify the URL of the request before it is performed."

View File

@ -0,0 +1,6 @@
---
type: add
issue: 1783
title: "A new client interceptor called UrlTenantSelectionInterceptor has been added. This interceptor allows the dynamic
selection of a tenant ID on servers that are using URL Base Tenant Selection."

View File

@ -0,0 +1,8 @@
---
type: add
issue: 1783
title: "In the JPA server, the ModelConfig setting 'DefaultSearchParamsCanBeOverridden' now has a default value of
*true* (previously this was *false*). In addition, when creating/updating a SearchParameter resource, the system
will now raise an error if the client is attempting to override a built-in SearchParameter when this setting
is disabled (previously this was silently ignored)."

View File

@ -6,6 +6,7 @@
<ul>
<li>Hibernate ORM (JPA): 5.4.6 -&gt; 5.4.12</li>
<li>Hibernate Search (JPA): 5.11.3 -&gt; 5.11.5</li>
<li>Hibernate Validator (JPA): 5.4.2.Final -&gt; 6.1.3.Final</li>
<li>Guava (JPA): 28.0 -&gt; 28.2</li>
</ul>"
- item:

View File

@ -75,6 +75,14 @@ The following example shows how to configure your client to inject a bearer toke
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientExamples.java|cookie}}
```
# Multitenancy: Add tenant ID to path
When communicating with a server that supports [URL Base Multitenancy](/docs/server_plain/multitenancy.html#url-base-multitenancy), an extra element needs to be added to the request path. This can be done by simply appending the path to the base URL supplied to the client, but it can also be dynamically appended using this interceptor.
* [UrlTenantSelectionInterceptor JavaDoc](/apidocs/hapi-fhir-client/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.html)
* [UrlTenantSelectionInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java)
# Performance: GZip Outgoing Request Bodies
The GZipContentInterceptor compresses outgoing contents. With this interceptor, if the client is transmitting resources to the server (e.g. for a create, update, transaction, etc.) the content will be GZipped before transmission to the server.

View File

@ -6,6 +6,8 @@ Partitioning is a relatively new feature in HAPI FHIR and has a number of known
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.
* **Server Capability Statement is not partition aware**: The server creates and exposes a single server capability statement, covering all partitions. This can be misleading when partitioning us used as a multitenancy strategy.
* **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:

View File

@ -4,7 +4,7 @@ If you wish to allow a single endpoint to support multiple tenants, you may supp
This means that additional logic will be performed during request parsing to determine a tenant ID, which will be supplied to resource providers. This can be useful in servers that have multiple distinct logical pools of resources hosted on the same infrastructure.
## URL Base Multitenancy
# URL Base Multitenancy
Using URL Base Multitenancy means that an additional element is added to the path of each resource between the server base URL and the resource name. For example, if your restful server is deployed to `http://acme.org:8080/baseDstu3` and a client wishes to access Patient 123 for Tenant "FOO", the resource ID (and URL to fetch that resource) would be `http://acme.org:8080/FOO/Patient/123`.

View File

@ -119,4 +119,9 @@ public class JaxRsHttpRequest implements IHttpRequest {
return ""; // TODO: can we get this from somewhere?
}
@Override
public void setUri(String theUrl) {
throw new UnsupportedOperationException();
}
}

View File

@ -187,10 +187,6 @@ public class DaoConfig {
*/
private boolean myDeleteEnabled = true;
/**
* @since 5.0.0
*/
private boolean myPartitioningEnabled;
/**
* Constructor
@ -1918,24 +1914,6 @@ public class DaoConfig {
return myDeleteEnabled;
}
/**
* If enabled (default is <code>false</code>) the JPA server will support data partitioning
*
* @since 5.0.0
*/
public void setPartitioningEnabled(boolean theMultiTenancyEnabled) {
myPartitioningEnabled = theMultiTenancyEnabled;
}
/**
* If enabled (default is <code>false</code>) the JPA server will support data partitioning
*
* @since 5.0.0
*/
public boolean isPartitioningEnabled() {
return myPartitioningEnabled;
}
public enum StoreMetaSourceInformationEnum {
NONE(false, false),
SOURCE_URI(true, false),

View File

@ -11,7 +11,9 @@ import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.partition.IPartitionConfigSvc;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
import ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl;
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
@ -19,6 +21,7 @@ import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
import ca.uhn.fhir.jpa.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory;
@ -202,7 +205,7 @@ public abstract class BaseConfig {
}
@Bean
public RequestPartitionHelperService requestPartitionHelperService() {
public IRequestPartitionHelperService requestPartitionHelperService() {
return new RequestPartitionHelperService();
}
@ -228,6 +231,11 @@ public abstract class BaseConfig {
return new JpaConsentContextServices();
}
@Bean
public PartitionConfig partitionConfig() {
return new PartitionConfig();
}
@Bean
@Lazy
public IPartitionConfigSvc partitionConfigSvc() {
@ -240,6 +248,12 @@ public abstract class BaseConfig {
return new PartitionManagementProvider();
}
@Bean
@Lazy
public RequestTenantPartitionInterceptor requestTenantPartitionInterceptor() {
return new RequestTenantPartitionInterceptor();
}
@Bean
@Lazy
public TerminologyUploaderProvider terminologyUploaderProvider() {

View File

@ -17,6 +17,7 @@ import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
@ -177,6 +178,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
private SearchBuilderFactory mySearchBuilderFactory;
private FhirContext myContext;
private ApplicationContext myApplicationContext;
@Autowired
private PartitionConfig myPartitionConfig;
@Override
protected IInterceptorBroadcaster getInterceptorBroadcaster() {
@ -983,7 +986,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (thePerformIndexing) {
newParams = new ResourceIndexedSearchParams();
mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theUpdateTime, entity, theResource, existingParams, theRequest);
mySearchParamWithInlineReferencesExtractor.populateFromResource(myPartitionConfig, newParams, theUpdateTime, entity, theResource, existingParams, theRequest);
changed = populateResourceIntoEntity(theRequest, theResource, entity, true);
if (changed.isChanged()) {

View File

@ -32,8 +32,8 @@ import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
import ca.uhn.fhir.jpa.model.entity.BaseTag;
@ -45,6 +45,7 @@ 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.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
@ -144,7 +145,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private String myResourceName;
private Class<T> myResourceType;
@Autowired
private RequestPartitionHelperService myRequestPartitionHelperService;
private IRequestPartitionHelperService myRequestPartitionHelperService;
@Autowired
private PartitionConfig myPartitionConfig;
@Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) {
@ -686,6 +689,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
if (myPartitionConfig.isPartitioningEnabled()) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "noSystemOrTypeHistoryForPartitionAwareServer");
throw new MethodNotAllowedException(msg);
}
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
notifyInterceptors(RestOperationTypeEnum.HISTORY_TYPE, requestDetails);

View File

@ -1,12 +1,15 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.StopWatch;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -47,6 +50,8 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
@Autowired
@Qualifier("myResourceCountsCache")
public ResourceCountCache myResourceCountsCache;
@Autowired
private PartitionConfig myPartitionConfig;
@Override
@Transactional(propagation = Propagation.NEVER)
@ -76,6 +81,11 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
@Override
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
if (myPartitionConfig.isPartitioningEnabled()) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "noSystemOrTypeHistoryForPartitionAwareServer");
throw new MethodNotAllowedException(msg);
}
if (theRequestDetails != null) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);

View File

@ -23,10 +23,10 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
@ -42,7 +42,10 @@ import org.apache.commons.lang3.Validate;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Scorer;
import org.apache.lucene.search.highlight.TokenGroup;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
@ -56,7 +59,12 @@ import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -273,7 +281,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
}
@Autowired
private RequestPartitionHelperService myRequestPartitionHelperService;
private IRequestPartitionHelperService myRequestPartitionHelperService;
@Transactional()
@Override

View File

@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
@ -169,6 +170,8 @@ public class SearchBuilder implements ISearchBuilder {
private Set<ResourcePersistentId> myPidSet;
private PredicateBuilder myPredicateBuilder;
private PartitionId myPartitionId;
@Autowired
private PartitionConfig myPartitionConfig;
/**
* Constructor
@ -483,7 +486,7 @@ public class SearchBuilder implements ISearchBuilder {
Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName());
theQueryRoot.addPredicate(joinParam1);
} else {
Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myResourceName, theSort.getParamName());
Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myPartitionConfig, myPartitionId, myResourceName, theSort.getParamName());
Predicate joinParam1 = theBuilder.equal(join.get("myHashIdentity"), hashIdentity);
theQueryRoot.addPredicate(joinParam1);
}

View File

@ -59,6 +59,20 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
@Query("SELECT f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId IN ( :forced_id )")
Collection<Object[]> findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
@Query("SELECT f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myPartitionIdValue = :partition_id AND myResourceType = :resource_type AND myForcedId IN ( :forced_id )")
Collection<Object[]> findByTypeAndForcedIdInPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId, @Param("partition_id") Integer thePartitionId);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
@Query("SELECT f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myPartitionIdValue IS NULL AND myResourceType = :resource_type AND myForcedId IN ( :forced_id )")
Collection<Object[]> findByTypeAndForcedIdInPartitionNull(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId);
/**
* Warning: No DB index exists for this particular query, so it may not perform well
*
@ -84,4 +98,31 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )")
Collection<Object[]> findAndResolveByForcedIdWithNoType(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
@Query("" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue = :partition_id")
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds, @Param("partition_id") Integer thePartitionId);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
@Query("" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IS NULL")
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionNull(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds);
}

View File

@ -67,4 +67,10 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
@Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid)")
Collection<Object[]> findLookupFieldsByResourcePid(@Param("pid") List<Long> thePids);
@Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue = :partition_id")
Collection<Object[]> findLookupFieldsByResourcePidInPartition(@Param("pid") List<Long> thePids, @Param("partition_id") Integer thePartitionId);
@Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue IS NULL")
Collection<Object[]> findLookupFieldsByResourcePidInPartitionNull(@Param("pid") List<Long> thePids);
}

View File

@ -30,6 +30,7 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -64,11 +65,11 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
private DaoRegistry myDaoRegistry;
@Override
public IResourceLookup findTargetResource(RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
public IResourceLookup findTargetResource(PartitionId thePartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
IResourceLookup resolvedResource;
String idPart = theSourceResourceId.getIdPart();
try {
resolvedResource = myIdHelperService.resolveResourceIdentity(theTypeString, idPart, theRequest);
resolvedResource = myIdHelperService.resolveResourceIdentity(thePartitionId, theTypeString, idPart, theRequest);
ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, resolvedResource);
} catch (ResourceNotFoundException e) {

View File

@ -61,7 +61,6 @@ public class DaoSearchParamSynchronizer {
}
private <T extends BaseResourceIndex> void synchronize(ResourceIndexedSearchParams theParams, ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection<T> theNewParms, Collection<T> theExistingParms) {
theParams.calculateHashes(theNewParms);
List<T> quantitiesToRemove = subtract(theExistingParms, theNewParms);
List<T> quantitiesToAdd = subtract(theNewParms, theExistingParms);
tryToReuseIndexEntities(quantitiesToRemove, quantitiesToAdd);
@ -71,6 +70,9 @@ public class DaoSearchParamSynchronizer {
}
for (T next : quantitiesToAdd) {
next.setPartitionId(theEntity.getPartitionId());
}
theParams.calculateHashes(theNewParms);
for (T next : quantitiesToAdd) {
myEntityManager.merge(next);
}

View File

@ -63,6 +63,7 @@ import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -122,10 +123,10 @@ public class IdHelperService {
* @throws ResourceNotFoundException If the ID can not be found
*/
@Nonnull
public IResourceLookup resolveResourceIdentity(String theResourceName, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException {
public IResourceLookup resolveResourceIdentity(PartitionId thePartitionId, String theResourceName, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException {
// We only pass 1 input in so only 0..1 will come back
IdDt id = new IdDt(theResourceName, theResourceId);
Collection<IResourceLookup> matches = translateForcedIdToPids(theRequestDetails, Collections.singletonList(id));
Collection<IResourceLookup> matches = translateForcedIdToPids(thePartitionId, theRequestDetails, Collections.singletonList(id));
assert matches.size() <= 1;
if (matches.isEmpty()) {
throw new ResourceNotFoundException(id);
@ -145,7 +146,7 @@ public class IdHelperService {
if (myDaoConfig.isDeleteEnabled()) {
retVal = resolveResourceIdentity(thePartitionId, theResourceType, theId);
} else {
String key = thePartitionId.getPartitionIdStringOrNullString() + "/" + theResourceType + "/" + theId;
String key = stringifyForKey(thePartitionId) + "/" + theResourceType + "/" + theId;
retVal = myPersistentIdCache.get(key, t -> resolveResourceIdentity(thePartitionId, theResourceType, theId));
}
@ -157,10 +158,13 @@ public class IdHelperService {
}
/**
* Given a collection of resource IDs (resource type + id), resolves the internal persistent IDs
* Given a collection of resource IDs (resource type + id), resolves the internal persistent IDs.
* <p>
* This implementation will always try to use a cache for performance, meaning that it can resolve resources that
* are deleted (but note that forced IDs can't change, so the cache can't return incorrect results)
*/
@Nonnull
public List<ResourcePersistentId> resolveResourcePersistentIds(List<IIdType> theIds, RequestDetails theRequest) {
public List<ResourcePersistentId> resolveResourcePersistentIdsWithCache(PartitionId thePartitionId, List<IIdType> theIds, RequestDetails theRequest) {
theIds.forEach(id -> Validate.isTrue(id.hasIdPart()));
if (theIds.isEmpty()) {
@ -190,29 +194,35 @@ public class IdHelperService {
} else {
if (!myDaoConfig.isDeleteEnabled()) {
for (Iterator<String> idIterator = nextIds.iterator(); idIterator.hasNext(); ) {
String nextId = idIterator.next();
String key = nextResourceType + "/" + nextId;
Long nextCachedPid = myPersistentIdCache.getIfPresent(key);
if (nextCachedPid != null) {
idIterator.remove();
retVal.add(new ResourcePersistentId(nextCachedPid));
}
for (Iterator<String> idIterator = nextIds.iterator(); idIterator.hasNext(); ) {
String nextId = idIterator.next();
String key = stringifyForKey(thePartitionId) + "/" + nextResourceType + "/" + nextId;
Long nextCachedPid = myPersistentIdCache.getIfPresent(key);
if (nextCachedPid != null) {
idIterator.remove();
retVal.add(new ResourcePersistentId(nextCachedPid));
}
}
if (nextIds.size() > 0) {
Collection<Object[]> views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
Collection<Object[]> views;
if (thePartitionId != null) {
if (thePartitionId.getPartitionId() != null) {
views = myForcedIdDao.findByTypeAndForcedIdInPartition(nextResourceType, nextIds, thePartitionId.getPartitionId());
} else {
views = myForcedIdDao.findByTypeAndForcedIdInPartitionNull(nextResourceType, nextIds);
}
} else {
views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
}
for (Object[] nextView : views) {
String forcedId = (String) nextView[0];
Long pid = (Long) nextView[1];
retVal.add(new ResourcePersistentId(pid));
if (!myDaoConfig.isDeleteEnabled()) {
String key = nextResourceType + "/" + forcedId;
myPersistentIdCache.put(key, pid);
}
String key = stringifyForKey(thePartitionId) + "/" + nextResourceType + "/" + forcedId;
myPersistentIdCache.put(key, pid);
}
}
@ -283,7 +293,7 @@ public class IdHelperService {
return pid.get();
}
private Collection<IResourceLookup> translateForcedIdToPids(RequestDetails theRequest, Collection<IIdType> theId) {
private Collection<IResourceLookup> translateForcedIdToPids(PartitionId thePartitionId, RequestDetails theRequest, Collection<IIdType> theId) {
theId.forEach(id -> Validate.isTrue(id.hasIdPart()));
if (theId.isEmpty()) {
@ -299,9 +309,19 @@ public class IdHelperService {
.map(t -> t.getIdPartAsLong())
.collect(Collectors.toList());
if (!pids.isEmpty()) {
myResourceTableDao.findLookupFieldsByResourcePid(pids)
Collection<Object[]> lookup;
if (thePartitionId != null) {
if (thePartitionId.getPartitionId() != null) {
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartition(pids, thePartitionId.getPartitionId());
} else {
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionNull(pids);
}
} else {
lookup = myResourceTableDao.findLookupFieldsByResourcePid(pids);
}
lookup
.stream()
.map(lookup -> new ResourceLookup((String) lookup[0], (Long) lookup[1], (Date) lookup[2]))
.map(t -> new ResourceLookup((String) t[0], (Long) t[1], (Date) t[2]))
.forEach(retVal::add);
}
}
@ -327,12 +347,20 @@ public class IdHelperService {
Collection<Object[]> views;
if (isBlank(nextResourceType)) {
warnAboutUnqualifiedForcedIdResolution(theRequest);
// FIXME: deal with partition here
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextIds);
} else {
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds);
if (thePartitionId != null) {
if (thePartitionId.getPartitionId() != null) {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(nextResourceType, nextIds, thePartitionId.getPartitionId());
} else {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(nextResourceType, nextIds);
}
} else {
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds);
}
}
for (Object[] next : views) {
@ -379,6 +407,14 @@ public class IdHelperService {
.build();
}
private static String stringifyForKey(PartitionId thePartitionId) {
String retVal = "(null)";
if (thePartitionId != null) {
retVal = thePartitionId.getPartitionIdStringOrNullString();
}
return retVal;
}
public static boolean isValidPid(IIdType theId) {
if (theId == null) {
return false;

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
@ -95,12 +96,12 @@ public class SearchParamWithInlineReferencesExtractor {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
public void populateFromResource(ResourceIndexedSearchParams theParams, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest) {
public void populateFromResource(PartitionConfig thePartitionConfig, ResourceIndexedSearchParams theParams, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest) {
mySearchParamExtractorService.extractFromResource(theRequest, theParams, theEntity, theResource);
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
theParams.findMissingSearchParams(myDaoConfig.getModelConfig(), theEntity, activeSearchParams);
theParams.findMissingSearchParams(thePartitionConfig, myDaoConfig.getModelConfig(), theEntity, activeSearchParams);
}
theParams.setUpdatedTime(theUpdateTime);

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
@ -52,17 +53,18 @@ import java.util.List;
abstract class BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(BasePredicateBuilder.class);
@Autowired
FhirContext myContext;
@Autowired
DaoConfig myDaoConfig;
boolean myDontUseHashesForSearch;
final CriteriaBuilder myCriteriaBuilder;
final QueryRoot myQueryRoot;
final Class<? extends IBaseResource> myResourceType;
final String myResourceName;
final SearchParameterMap myParams;
@Autowired
FhirContext myContext;
@Autowired
DaoConfig myDaoConfig;
boolean myDontUseHashesForSearch;
@Autowired
private PartitionConfig myPartitionConfig;
BasePredicateBuilder(SearchBuilder theSearchBuilder) {
myCriteriaBuilder = theSearchBuilder.getBuilder();
@ -117,7 +119,7 @@ abstract class BasePredicateBuilder {
Join<ResourceTable, SearchParamPresent> paramPresentJoin = myQueryRoot.join("mySearchParamPresents", JoinType.LEFT);
Expression<Long> hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
Long hash = SearchParamPresent.calculateHashPresence(theResourceName, theParamName, !theMissing);
Long hash = SearchParamPresent.calculateHashPresence(myPartitionConfig, thePartitionId, theResourceName, theParamName, !theMissing);
List<Predicate> predicates = new ArrayList<>();
predicates.add(myCriteriaBuilder.equal(hashPresence, hash));
@ -142,7 +144,7 @@ abstract class BasePredicateBuilder {
myQueryRoot.setHasIndexJoins();
}
Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate) {
Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate, PartitionId thePartitionId) {
if (myDontUseHashesForSearch) {
Predicate resourceTypePredicate = myCriteriaBuilder.equal(theFrom.get("myResourceType"), theResourceName);
Predicate paramNamePredicate = myCriteriaBuilder.equal(theFrom.get("myParamName"), theParamName);
@ -150,11 +152,15 @@ abstract class BasePredicateBuilder {
return outerPredicate;
}
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myPartitionConfig, thePartitionId, theResourceName, theParamName);
Predicate hashIdentityPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity);
return myCriteriaBuilder.and(hashIdentityPredicate, thePredicate);
}
public PartitionConfig getPartitionConfig() {
return myPartitionConfig;
}
Predicate createPredicateNumeric(String theResourceName,
String theParamName,
From<?, ? extends BaseResourceIndexedSearchParam> theFrom,
@ -163,7 +169,7 @@ abstract class BasePredicateBuilder {
ParamPrefixEnum thePrefix,
BigDecimal theValue,
final Expression<BigDecimal> thePath,
String invalidMessageName) {
String invalidMessageName, PartitionId thePartitionId) {
Predicate num;
// Per discussions with Grahame Grieve and James Agnew on 11/13/19, modified logic for EQUAL and NOT_EQUAL operators below so as to
// use exact value matching. The "fuzz amount" matching is still used with the APPROXIMATE operator.
@ -207,13 +213,13 @@ abstract class BasePredicateBuilder {
if (theParamName == null) {
return num;
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num, thePartitionId);
}
void addPartitionIdPredicate(PartitionId thePartitionId, Join<ResourceTable, ? extends BasePartitionable> theJoin, List<Predicate> theCodePredicates) {
if (thePartitionId != null) {
Integer partitionId = thePartitionId.getPartitionId();
Predicate partitionPredicate;
Predicate partitionPredicate;
if (partitionId != null) {
partitionPredicate = myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue").as(Integer.class), partitionId);
} else {

View File

@ -111,19 +111,19 @@ public class PredicateBuilder {
return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, thePartitionId);
}
Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theStringJoin) {
return myPredicateBuilderString.createPredicateString(theLeftValue, theResourceName, theName, theBuilder, theStringJoin);
Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theStringJoin, PartitionId thePartitionId) {
return myPredicateBuilderString.createPredicateString(theLeftValue, theResourceName, theName, theBuilder, theStringJoin, thePartitionId);
}
Collection<Predicate> createPredicateToken(List<IQueryParameterType> theTokens, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> theTokenJoin) {
return myPredicateBuilderToken.createPredicateToken(theTokens, theResourceName, theName, theBuilder, theTokenJoin);
Collection<Predicate> createPredicateToken(List<IQueryParameterType> theTokens, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> theTokenJoin, PartitionId thePartitionId) {
return myPredicateBuilderToken.createPredicateToken(theTokens, theResourceName, theName, theBuilder, theTokenJoin, thePartitionId);
}
Predicate createPredicateDate(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> theDateJoin) {
return myPredicateBuilderDate.createPredicateDate(theLeftValue, theResourceName, theName, theBuilder, theDateJoin);
Predicate createPredicateDate(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> theDateJoin, PartitionId thePartitionId) {
return myPredicateBuilderDate.createPredicateDate(theLeftValue, theResourceName, theName, theBuilder, theDateJoin, thePartitionId);
}
Predicate createPredicateQuantity(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> theDateJoin) {
return myPredicateBuilderQuantity.createPredicateQuantity(theLeftValue, theResourceName, theName, theBuilder, theDateJoin);
Predicate createPredicateQuantity(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> theDateJoin, PartitionId thePartitionId) {
return myPredicateBuilderQuantity.createPredicateQuantity(theLeftValue, theResourceName, theName, theBuilder, theDateJoin, thePartitionId);
}
}

View File

@ -59,7 +59,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamCoords> theFrom) {
From<?, ResourceIndexedSearchParamCoords> theFrom, PartitionId thePartitionId) {
String latitudeValue;
String longitudeValue;
Double distanceKm = 0.0;
@ -120,7 +120,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
longitudePredicate = longitudePredicateFromBox(theBuilder, theFrom, box);
}
Predicate singleCode = theBuilder.and(latitudePredicate, longitudePredicate);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
}
private Predicate latitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, SearchBox theBox) {
@ -166,8 +166,8 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
theResourceName,
theParamName,
myCriteriaBuilder,
join
);
join,
thePartitionId);
codePredicates.add(singleCode);
}

View File

@ -91,7 +91,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
theParamName,
myCriteriaBuilder,
join,
operation);
operation,
thePartitionId);
codePredicates.add(p);
}
@ -99,7 +100,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
myQueryRoot.setHasIndexJoins();
if (newJoin) {
Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, orPredicates);
Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, orPredicates, thePartitionId);
myQueryRoot.addPredicate(identityAndValuePredicate);
} else {
myQueryRoot.addPredicate(orPredicates);
@ -112,14 +113,15 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom) {
From<?, ResourceIndexedSearchParamDate> theFrom,
PartitionId thePartitionId) {
Predicate predicateDate = createPredicateDate(theParam,
theResourceName,
theParamName,
theBuilder,
theFrom,
null);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, predicateDate);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, predicateDate, thePartitionId);
}
private Predicate createPredicateDate(IQueryParameterType theParam,
@ -127,7 +129,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom,
SearchFilterParser.CompareOperation theOperation) {
SearchFilterParser.CompareOperation theOperation,
PartitionId thePartitionId) {
Predicate p;
if (theParam instanceof DateParam) {
@ -155,6 +158,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
return p;
}
// FIXME: does this need a partition ID?
private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom,
DateRangeParam theRange,

View File

@ -98,8 +98,8 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB
String invalidMessageName = "invalidNumberPrefix";
Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myCriteriaBuilder, nextOr, prefix, value, fromObj, invalidMessageName);
Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, predicateNumeric);
Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myCriteriaBuilder, nextOr, prefix, value, fromObj, invalidMessageName, thePartitionId);
Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, predicateNumeric, thePartitionId);
codePredicates.add(predicateOuter);
} else {

View File

@ -32,7 +32,11 @@ import ca.uhn.fhir.rest.param.QuantityParam;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@ -66,13 +70,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
addPartitionIdPredicate(thePartitionId, join, codePredicates);
for (IQueryParameterType nextOr : theList) {
Predicate singleCode = createPredicateQuantity(nextOr,
theResourceName,
theParamName,
myCriteriaBuilder,
join,
theOperation);
Predicate singleCode = createPredicateQuantity(nextOr, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, thePartitionId);
codePredicates.add(singleCode);
}
@ -86,13 +84,15 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamQuantity> theFrom) {
From<?, ResourceIndexedSearchParamQuantity> theFrom,
PartitionId thePartitionId) {
return createPredicateQuantity(theParam,
theResourceName,
theParamName,
theBuilder,
theFrom,
null);
null,
thePartitionId);
}
private Predicate createPredicateQuantity(IQueryParameterType theParam,
@ -100,7 +100,8 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamQuantity> theFrom,
SearchFilterParser.CompareOperation operation) {
SearchFilterParser.CompareOperation operation,
PartitionId thePartitionId) {
String systemValue;
String unitsValue;
ParamPrefixEnum cmpValue = null;
@ -157,7 +158,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
final Expression<BigDecimal> path = theFrom.get("myValue");
String invalidMessageName = "invalidQuantityPrefix";
Predicate num = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName);
Predicate num = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName, thePartitionId);
Predicate singleCode;
if (system == null && code == null) {
@ -170,18 +171,18 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
singleCode = theBuilder.and(system, code, num);
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
}
Predicate hashPredicate;
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(theResourceName, theParamName, systemValue, unitsValue);
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(getPartitionConfig(), thePartitionId, theResourceName, theParamName, systemValue, unitsValue);
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentitySystemAndUnits"), hash);
} else if (!isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(theResourceName, theParamName, unitsValue);
long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(getPartitionConfig(), thePartitionId, theResourceName, theParamName, unitsValue);
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentityAndUnits"), hash);
} else {
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionConfig(), thePartitionId, theResourceName, theParamName);
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hash);
}
@ -189,7 +190,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
final Expression<BigDecimal> path = theFrom.get("myValue");
String invalidMessageName = "invalidQuantityPrefix";
Predicate numericPredicate = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName);
Predicate numericPredicate = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName, thePartitionId);
return theBuilder.and(hashPredicate, numericPredicate);
}

View File

@ -36,6 +36,7 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
@ -63,6 +64,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -108,6 +110,8 @@ class PredicateBuilderReference extends BasePredicateBuilder {
@Autowired
DaoRegistry myDaoRegistry;
@Autowired
PartitionConfig myPartitionConfig;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
PredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
@ -190,7 +194,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
addPartitionIdPredicate(thePartitionId, join, codePredicates);
// Resources by ID
List<ResourcePersistentId> targetPids = myIdHelperService.resolveResourcePersistentIds(targetIds, theRequest);
List<ResourcePersistentId> targetPids = myIdHelperService.resolveResourcePersistentIdsWithCache(thePartitionId, targetIds, theRequest);
if (!targetPids.isEmpty()) {
ourLog.debug("Searching for resource link with target PIDs: {}", targetPids);
Predicate pathPredicate;
@ -201,7 +205,11 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
Predicate pidPredicate;
if ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) {
pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids));
if (targetPids.size() == 1) {
pidPredicate = myCriteriaBuilder.equal(join.get("myTargetResourcePid").as(Long.class), targetPids.get(0).getIdAsLong());
} else {
pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids));
}
} else {
pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids)).not();
}
@ -541,6 +549,13 @@ class PredicateBuilderReference extends BasePredicateBuilder {
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
if (nextParamDef != null) {
if (myPartitionConfig.isPartitioningEnabled() && myPartitionConfig.isIncludePartitionInSearchHashes()) {
if (thePartitionId == null) {
throw new PreconditionFailedException("This server is not configured to support search against all partitions");
}
}
switch (nextParamDef.getParamType()) {
case DATE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
@ -578,7 +593,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateComposite(theResourceName, nextParamDef, nextAnd);
addPredicateComposite(theResourceName, nextParamDef, nextAnd, thePartitionId);
}
break;
case URI:
@ -623,6 +638,12 @@ class PredicateBuilderReference extends BasePredicateBuilder {
myQueryRoot.clearPredicates();
myQueryRoot.addPredicates(holdPredicates);
myQueryRoot.addPredicate(filterPredicate);
// Because filters can have an OR at the root, we never know for sure that we haven't done an optimized
// search that doesn't check the resource type. This could be improved in the future, but for now it's
// safest to just clear this flag. The test "testRetrieveDifferentTypeEq" will fail if we don't clear
// this here.
myQueryRoot.clearHasIndexJoins();
}
}
@ -932,7 +953,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return parameter.substring(parameter.indexOf(".") + 1);
}
private void addPredicateComposite(String theResourceName, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd) {
private void addPredicateComposite(String theResourceName, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, PartitionId thePartitionId) {
// TODO: fail if missing is set for a composite query
IQueryParameterType or = theNextAnd.get(0);
@ -943,37 +964,37 @@ class PredicateBuilderReference extends BasePredicateBuilder {
RuntimeSearchParam left = theParamDef.getCompositeOf().get(0);
IQueryParameterType leftValue = cp.getLeftValue();
myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), left, leftValue));
myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), left, leftValue, thePartitionId));
RuntimeSearchParam right = theParamDef.getCompositeOf().get(1);
IQueryParameterType rightValue = cp.getRightValue();
myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), right, rightValue));
myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), right, rightValue, thePartitionId));
}
private Predicate createCompositeParamPart(String theResourceName, Root<ResourceTable> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue) {
private Predicate createCompositeParamPart(String theResourceName, Root<ResourceTable> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue, PartitionId thePartitionId) {
Predicate retVal = null;
switch (theParam.getParamType()) {
case STRING: {
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER);
retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, stringJoin);
retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, stringJoin, thePartitionId);
break;
}
case TOKEN: {
From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER);
List<IQueryParameterType> tokens = Collections.singletonList(leftValue);
Collection<Predicate> tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam.getName(), myCriteriaBuilder, tokenJoin);
Collection<Predicate> tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam.getName(), myCriteriaBuilder, tokenJoin, thePartitionId);
retVal = myCriteriaBuilder.and(tokenPredicates.toArray(new Predicate[0]));
break;
}
case DATE: {
From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER);
retVal = myPredicateBuilder.createPredicateDate(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin);
retVal = myPredicateBuilder.createPredicateDate(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin, thePartitionId);
break;
}
case QUANTITY: {
From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER);
retVal = myPredicateBuilder.createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin);
retVal = myPredicateBuilder.createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin, thePartitionId);
break;
}
case COMPOSITE:

View File

@ -71,13 +71,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
addPartitionIdPredicate(thePartitionId, join, codePredicates);
for (IQueryParameterType nextOr : theList) {
IQueryParameterType theParameter = nextOr;
Predicate singleCode = createPredicateString(theParameter,
theResourceName,
theParamName,
myCriteriaBuilder,
join,
theOperation);
Predicate singleCode = createPredicateString(nextOr, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, thePartitionId);
codePredicates.add(singleCode);
}
@ -93,13 +87,15 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom) {
From<?, ResourceIndexedSearchParamString> theFrom,
PartitionId thePartitionId) {
return createPredicateString(theParameter,
theResourceName,
theParamName,
theBuilder,
theFrom,
null);
null,
thePartitionId);
}
private Predicate createPredicateString(IQueryParameterType theParameter,
@ -107,7 +103,8 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom,
SearchFilterParser.CompareOperation operation) {
SearchFilterParser.CompareOperation operation,
PartitionId thePartitionId) {
String rawSearchTerm;
if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter;
@ -157,12 +154,12 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
singleCode = theBuilder.and(singleCode, exactCode);
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
}
boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact();
if (exactMatch) {
// Exact match
Long hash = ResourceIndexedSearchParamString.calculateHashExact(theResourceName, theParamName, rawSearchTerm);
Long hash = ResourceIndexedSearchParamString.calculateHashExact(getPartitionConfig(), thePartitionId, theResourceName, theParamName, rawSearchTerm);
return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash);
} else {
// Normalized Match
@ -190,34 +187,34 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
Predicate predicate;
if ((operation == null) ||
(operation == SearchFilterParser.CompareOperation.sw)) {
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString);
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionConfig(), thePartitionId, myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString);
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = theBuilder.and(hashCode, singleCode);
} else if ((operation == SearchFilterParser.CompareOperation.ew) ||
(operation == SearchFilterParser.CompareOperation.co)) {
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
} else if (operation == SearchFilterParser.CompareOperation.eq) {
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString);
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionConfig(), thePartitionId, myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString);
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), normalizedString);
predicate = theBuilder.and(hashCode, singleCode);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
Predicate singleCode = theBuilder.notEqual(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
} else if (operation == SearchFilterParser.CompareOperation.gt) {
Predicate singleCode = theBuilder.greaterThan(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
} else if (operation == SearchFilterParser.CompareOperation.lt) {
Predicate singleCode = theBuilder.lessThan(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
Predicate singleCode = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
} else if (operation == SearchFilterParser.CompareOperation.le) {
Predicate singleCode = theBuilder.lessThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, thePartitionId);
} else {
throw new IllegalArgumentException("Don't yet know how to handle operation " + operation + " on a string");
}

View File

@ -103,7 +103,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
addPartitionIdPredicate(thePartitionId, join, codePredicates);
Collection<Predicate> singleCode = createPredicateToken(tokens, theResourceName, theParamName, myCriteriaBuilder, join, theOperation);
Collection<Predicate> singleCode = createPredicateToken(tokens, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, thePartitionId);
assert singleCode != null;
codePredicates.addAll(singleCode);
@ -119,14 +119,16 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamToken> theFrom) {
From<?, ResourceIndexedSearchParamToken> theFrom,
PartitionId thePartitionId) {
return createPredicateToken(
theParameters,
theResourceName,
theParamName,
theBuilder,
theFrom,
null);
null,
thePartitionId);
}
private Collection<Predicate> createPredicateToken(Collection<IQueryParameterType> theParameters,
@ -134,7 +136,8 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamToken> theFrom,
SearchFilterParser.CompareOperation operation) {
SearchFilterParser.CompareOperation operation,
PartitionId thePartitionId) {
final List<VersionIndependentConcept> codes = new ArrayList<>();
TokenParamModifier modifier = null;
@ -210,19 +213,19 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
// System only
List<VersionIndependentConcept> systemOnlyCodes = sortedCodesList.stream().filter(t -> isBlank(t.getCode())).collect(Collectors.toList());
if (!systemOnlyCodes.isEmpty()) {
retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY));
retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY, thePartitionId));
}
// Code only
List<VersionIndependentConcept> codeOnlyCodes = sortedCodesList.stream().filter(t -> t.getSystem() == null).collect(Collectors.toList());
if (!codeOnlyCodes.isEmpty()) {
retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY));
retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY, thePartitionId));
}
// System and code
List<VersionIndependentConcept> systemAndCodeCodes = sortedCodesList.stream().filter(t -> isNotBlank(t.getCode()) && t.getSystem() != null).collect(Collectors.toList());
if (!systemAndCodeCodes.isEmpty()) {
retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE));
retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE, thePartitionId));
}
return retVal;
@ -274,7 +277,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
}
}
private Predicate addPredicate(String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamToken> theFrom, List<VersionIndependentConcept> theTokens, TokenParamModifier theModifier, SearchBuilderTokenModeEnum theTokenMode) {
private Predicate addPredicate(String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamToken> theFrom, List<VersionIndependentConcept> theTokens, TokenParamModifier theModifier, SearchBuilderTokenModeEnum theTokenMode, PartitionId thePartitionId) {
if (myDontUseHashesForSearch) {
final Path<String> systemExpression = theFrom.get("mySystem");
final Path<String> valueExpression = theFrom.get("myValue");
@ -307,7 +310,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
or = theBuilder.not(or);
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, or);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, or, thePartitionId);
}
/*
@ -322,14 +325,14 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
hashField = theFrom.get("myHashSystem").as(Long.class);
values = theTokens
.stream()
.map(t -> ResourceIndexedSearchParamToken.calculateHashSystem(theResourceName, theParamName, t.getSystem()))
.map(t -> ResourceIndexedSearchParamToken.calculateHashSystem(getPartitionConfig(), thePartitionId, theResourceName, theParamName, t.getSystem()))
.collect(Collectors.toList());
break;
case VALUE_ONLY:
hashField = theFrom.get("myHashValue").as(Long.class);
values = theTokens
.stream()
.map(t -> ResourceIndexedSearchParamToken.calculateHashValue(theResourceName, theParamName, t.getCode()))
.map(t -> ResourceIndexedSearchParamToken.calculateHashValue(getPartitionConfig(), thePartitionId, theResourceName, theParamName, t.getCode()))
.collect(Collectors.toList());
break;
case SYSTEM_AND_VALUE:
@ -337,14 +340,14 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
hashField = theFrom.get("myHashSystemAndValue").as(Long.class);
values = theTokens
.stream()
.map(t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(theResourceName, theParamName, t.getSystem(), t.getCode()))
.map(t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(getPartitionConfig(), thePartitionId, theResourceName, theParamName, t.getSystem(), t.getCode()))
.collect(Collectors.toList());
break;
}
Predicate predicate = hashField.in(values);
if (theModifier == TokenParamModifier.NOT) {
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName));
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionConfig(), thePartitionId, theResourceName, theParamName));
Predicate disjunctionPredicate = theBuilder.not(predicate);
predicate = theBuilder.and(identityPredicate, disjunctionPredicate);
}

View File

@ -108,13 +108,13 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
}
Predicate uriPredicate = join.get("myUri").as(String.class).in(toFind);
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate);
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate, thePartitionId);
codePredicates.add(hashAndUriPredicate);
} else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
Predicate uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate);
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate, thePartitionId);
codePredicates.add(hashAndUriPredicate);
} else {
@ -125,7 +125,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
Predicate uriPredicate = null;
if (operation == null || operation == SearchFilterParser.CompareOperation.eq) {
long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(theResourceName, theParamName, value);
long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(getPartitionConfig(), thePartitionId, theResourceName, theParamName, value);
Predicate hashPredicate = myCriteriaBuilder.equal(join.get("myHashUri"), hashUri);
codePredicates.add(hashPredicate);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
@ -150,7 +150,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
}
if (uriPredicate != null) {
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionConfig(), thePartitionId, theResourceName, theParamName);
Predicate hashIdentityPredicate = myCriteriaBuilder.equal(join.get("myHashIdentity"), hashIdentity);
codePredicates.add(myCriteriaBuilder.and(hashIdentityPredicate, uriPredicate));
}
@ -179,7 +179,8 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName,
theParamName,
join,
orPredicate);
orPredicate,
thePartitionId);
myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(outerPredicate);
return outerPredicate;

View File

@ -99,4 +99,8 @@ public class QueryRoot {
public void setHasIndexJoins() {
myHasIndexJoins = true;
}
public void clearHasIndexJoins() {
myHasIndexJoins = false;
}
}

View File

@ -156,7 +156,7 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<Se
for (IPrimitiveType<?> nextBaseType : theBase) {
String nextBase = nextBaseType.getValueAsString();
RuntimeSearchParam existingSearchParam = theSearchParamRegistry.getActiveSearchParam(nextBase, theCode);
if (existingSearchParam.getId() == null) {
if (existingSearchParam != null && existingSearchParam.getId() == null) {
throw new UnprocessableEntityException("Can not override built-in search parameter " + nextBase + ":" + theCode + " because overriding is disabled on this server");
}
}

View File

@ -23,4 +23,5 @@ public interface IPartitionConfigSvc {
PartitionEntity updatePartition(PartitionEntity thePartition);
void deletePartition(Integer thePartitionId);
}

View File

@ -0,0 +1,16 @@
package ca.uhn.fhir.jpa.partition;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public interface IRequestPartitionHelperService {
@Nullable
PartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType);
@Nullable
PartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource);
}

View File

@ -27,7 +27,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class PartitionConfigSvcImpl implements IPartitionConfigSvc {
public static final int DEFAULT_PERSISTED_PARTITION_ID = 0;
private static final String DEFAULT_PERSISTED_PARTITION_NAME = "DEFAULT";
public static final String DEFAULT_PERSISTED_PARTITION_NAME = "DEFAULT";
private static final String DEFAULT_PERSISTED_PARTITION_DESC = "Default partition";
private static final Pattern PARTITION_NAME_VALID_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+");
private static final Logger ourLog = LoggerFactory.getLogger(PartitionConfigSvcImpl.class);

View File

@ -12,8 +12,6 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class PartitionManagementProvider {
@ -50,7 +48,7 @@ public class PartitionManagementProvider {
* $partition-management-update-partition
* </code>
*/
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_UPDATE_PARTITION)
public IBaseParameters updatePartition(
@ResourceParam IBaseParameters theRequest,
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId,
@ -71,7 +69,7 @@ public class PartitionManagementProvider {
* $partition-management-delete-partition
* </code>
*/
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_DELETE_PARTITION)
public IBaseParameters updatePartition(
@ResourceParam IBaseParameters theRequest,
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId

View File

@ -5,6 +5,7 @@ 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.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -19,7 +20,7 @@ import java.util.HashSet;
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooksAndReturnObject;
public class RequestPartitionHelperService {
public class RequestPartitionHelperService implements IRequestPartitionHelperService {
private final HashSet<Object> myPartitioningBlacklist;
@ -31,6 +32,8 @@ public class RequestPartitionHelperService {
private IPartitionConfigSvc myPartitionConfigSvc;
@Autowired
private FhirContext myFhirContext;
@Autowired
private PartitionConfig myPartitionConfig;
public RequestPartitionHelperService() {
myPartitioningBlacklist = new HashSet<>();
@ -53,6 +56,7 @@ public class RequestPartitionHelperService {
* Invoke the <code>STORAGE_PARTITION_IDENTIFY_READ</code> interceptor pointcut to determine the tenant for a read request
*/
@Nullable
@Override
public PartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType) {
if (myPartitioningBlacklist.contains(theResourceType)) {
return null;
@ -60,14 +64,14 @@ public class RequestPartitionHelperService {
PartitionId partitionId = null;
if (myDaoConfig.isPartitioningEnabled()) {
if (myPartitionConfig.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, theRequest);
validatePartition(partitionId, theResourceType);
}
return partitionId;
@ -77,10 +81,11 @@ public class RequestPartitionHelperService {
* Invoke the <code>STORAGE_PARTITION_IDENTIFY_CREATE</code> interceptor pointcut to determine the tenant for a read request
*/
@Nullable
@Override
public PartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource) {
PartitionId partitionId = null;
if (myDaoConfig.isPartitioningEnabled()) {
if (myPartitionConfig.isPartitioningEnabled()) {
// Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE
HookParams params = new HookParams()
.add(IBaseResource.class, theResource)
@ -89,13 +94,13 @@ public class RequestPartitionHelperService {
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
validatePartition(partitionId, resourceName, theRequest);
validatePartition(partitionId, resourceName);
}
return partitionId;
}
private void validatePartition(@Nullable PartitionId thePartitionId, @Nonnull String theResourceName, RequestDetails theRequestDetails) {
private void validatePartition(@Nullable PartitionId thePartitionId, @Nonnull String theResourceName) {
if (thePartitionId != null && thePartitionId.getPartitionId() != null) {
// Make sure we're not using one of the conformance resources in a non-default partition

View File

@ -11,7 +11,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
public class RequestTenantSelectingInterceptor {
public class RequestTenantPartitionInterceptor {
@Autowired
private IPartitionConfigSvc myPartitionConfigSvc;
@ -36,7 +36,7 @@ public class RequestTenantSelectingInterceptor {
try {
partition = myPartitionConfigSvc.getPartitionByName(tenantId);
} catch (IllegalArgumentException e) {
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestTenantSelectingInterceptor.class, "unknownTenantName", tenantId);
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestTenantPartitionInterceptor.class, "unknownTenantName", tenantId);
throw new ResourceNotFoundException(msg);
}

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.IResultIterator;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchInclude;
@ -156,7 +157,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
@Autowired
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
@Autowired
private RequestPartitionHelperService myRequestPartitionHelperService;
private IRequestPartitionHelperService myRequestPartitionHelperService;
/**
* Constructor
@ -593,6 +594,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
myPersistedJpaBundleProviderFactory = thePersistedJpaBundleProviderFactory;
}
@VisibleForTesting
public void setRequestPartitionHelperService(IRequestPartitionHelperService theRequestPartitionHelperService) {
myRequestPartitionHelperService = theRequestPartitionHelperService;
}
/**
* A search task is a Callable task that runs in
* a thread pool to handle an individual search. One instance

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.sp;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.util.AddRemoveCount;
@ -37,6 +38,9 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
@Autowired
private ISearchParamPresentDao mySearchParamPresentDao;
@Autowired
private PartitionConfig myPartitionConfig;
@Autowired
private DaoConfig myDaoConfig;
@ -63,6 +67,7 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
String paramName = next.getKey();
SearchParamPresent present = new SearchParamPresent();
present.setPartitionConfig(myPartitionConfig);
present.setResource(theResource);
present.setParamName(paramName);
present.setPresent(next.getValue());

View File

@ -104,6 +104,10 @@ public class TestUtil {
Subselect subselect = theClazz.getAnnotation(Subselect.class);
boolean isView = (subselect != null);
// FIXME: remove?
Embeddable embeddable = theClazz.getAnnotation(Embeddable.class);
boolean isEmbeddable = (embeddable != null);
scan(theClazz, theNames, theIsSuperClass, isView);
for (Field nextField : theClazz.getDeclaredFields()) {
@ -126,11 +130,13 @@ public class TestUtil {
boolean hasColumn = nextField.getAnnotation(Column.class) != null;
boolean hasJoinColumn = nextField.getAnnotation(JoinColumn.class) != null;
boolean hasEmbeddedId = nextField.getAnnotation(EmbeddedId.class) != null;
boolean hasEmbedded = nextField.getAnnotation(Embedded.class) != null;
OneToMany oneToMany = nextField.getAnnotation(OneToMany.class);
OneToOne oneToOne = nextField.getAnnotation(OneToOne.class);
boolean isOtherSideOfOneToManyMapping = oneToMany != null && isNotBlank(oneToMany.mappedBy());
boolean isOtherSideOfOneToOneMapping = oneToOne != null && isNotBlank(oneToOne.mappedBy());
Validate.isTrue(
hasEmbedded ||
hasColumn ||
hasJoinColumn ||
isOtherSideOfOneToManyMapping ||

View File

@ -111,7 +111,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
SLF4JLogLevel level = SLF4JLogLevel.INFO;
DataSource dataSource = ProxyDataSourceBuilder
.create(retVal)
.logQueryBySlf4j(level, "SQL")
// .logQueryBySlf4j(level, "SQL")
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
// .countQuery(new ThreadQueryCountHolder())
.beforeQuery(new BlockLargeNumbersOfParamsListener())

View File

@ -243,48 +243,6 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
}
@Test
public void testOverrideAndDisableBuiltInSearchParametersWithOverridingDisabled() {
myModelConfig.setDefaultSearchParamsCanBeOverridden(false);
SearchParameter memberSp = new SearchParameter();
memberSp.setCode("member");
memberSp.setBase(ResourceTypeEnum.GROUP);
memberSp.setType(SearchParamTypeEnum.REFERENCE);
memberSp.setXpath("Group.member.entity");
memberSp.setXpathUsage(XPathUsageTypeEnum.NORMAL);
memberSp.setStatus(ConformanceResourceStatusEnum.RETIRED);
mySearchParameterDao.create(memberSp, mySrd);
SearchParameter identifierSp = new SearchParameter();
identifierSp.setCode("identifier");
identifierSp.setBase(ResourceTypeEnum.GROUP);
identifierSp.setType(SearchParamTypeEnum.TOKEN);
identifierSp.setXpath("Group.identifier");
identifierSp.setXpathUsage(XPathUsageTypeEnum.NORMAL);
identifierSp.setStatus(ConformanceResourceStatusEnum.RETIRED);
mySearchParameterDao.create(identifierSp, mySrd);
mySearchParamRegistry.forceRefresh();
Patient p = new Patient();
p.addName().addGiven("G");
IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless();
Group g = new Group();
g.addIdentifier().setSystem("urn:foo").setValue("bar");
g.addMember().getEntity().setReference(pid);
myGroupDao.create(g);
assertThat(myResourceLinkDao.findAll(), not(empty()));
assertThat(ListUtil.filter(myResourceIndexedSearchParamTokenDao.findAll(), new ListUtil.Filter<ResourceIndexedSearchParamToken>() {
@Override
public boolean isOut(ResourceIndexedSearchParamToken object) {
return !object.getResourceType().equals("Group") || object.isMissing();
}
}), not(empty()));
}
@Test
public void testOverrideAndDisableBuiltInSearchParametersWithOverridingEnabled() {
myModelConfig.setDefaultSearchParamsCanBeOverridden(true);

View File

@ -212,7 +212,7 @@ public class FhirResourceDaoDstu2UpdateTest extends BaseJpaDstu2Test {
p2.setId(new IdDt("Patient/" + p1id.getIdPart()));
myOrganizationDao.update(p2, mySrd);
fail();
} catch (UnprocessableEntityException e) {
} catch (InvalidRequestException e) {
ourLog.error("Good", e);
}

View File

@ -582,15 +582,15 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test {
myOrganizationDao.update(p2, mySrd);
fail();
} catch (UnprocessableEntityException e) {
// good
assertEquals("Existing resource ID[Patient/1] is of type[Patient] - Cannot update with [Organization]", e.getMessage());
}
try {
p2.setId(new IdType("Patient/" + p1id.getIdPart()));
myOrganizationDao.update(p2, mySrd);
fail();
} catch (UnprocessableEntityException e) {
ourLog.error("Good", e);
} catch (InvalidRequestException e) {
assertEquals("Incorrect resource type (Patient) for this DAO, wanted: Organization", e.getMessage());
}
}

View File

@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@ -188,6 +189,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Autowired
protected DaoConfig myDaoConfig;
@Autowired
protected PartitionConfig myPartitionConfig;
@Autowired
protected ModelConfig myModelConfig;
@Autowired
@Qualifier("myDeviceDaoR4")

View File

@ -9,7 +9,12 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.CarePlan;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.RiskAssessment;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@ -18,7 +23,9 @@ import org.junit.Test;
import java.util.List;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@SuppressWarnings({"Duplicates"})
public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
@ -183,22 +190,21 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam(String.format("status eq active or _id eq %s",
idVal)));
map.add(Constants.PARAM_FILTER, new StringParam(String.format("status eq active or _id eq %s", idVal)));
myCaptureQueriesListener.clear();
found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(found, Matchers.empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s", idVal)));
found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map));
assertThat(found, Matchers.empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s",
idVal)));
found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map));
assertThat(found, Matchers.empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s",
idVal)));
map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s", idVal)));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, containsInAnyOrder(id1));

View File

@ -265,7 +265,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
assertEquals(1, myObservationDao.search(map).size().intValue());
// myCaptureQueriesListener.logAllQueriesForCurrentThread();
// Resolve forced ID, Perform search, load result
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
@ -279,8 +278,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
assertEquals(1, myObservationDao.search(map).size().intValue());
myCaptureQueriesListener.logAllQueriesForCurrentThread();
// Resolve forced ID, Perform search, load result
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
// Resolve forced ID, Perform search, load result (this time we reuse the cached forced-id resolution)
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());

View File

@ -344,34 +344,12 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
memberSp.setType(Enumerations.SearchParamType.REFERENCE);
memberSp.setExpression("Group.member.entity");
memberSp.setStatus(Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(memberSp, mySrd);
SearchParameter identifierSp = new SearchParameter();
identifierSp.setCode("identifier");
identifierSp.addBase("Group");
identifierSp.setType(Enumerations.SearchParamType.TOKEN);
identifierSp.setExpression("Group.identifier");
identifierSp.setStatus(Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(identifierSp, mySrd);
mySearchParamRegistry.forceRefresh();
Patient p = new Patient();
p.addName().addGiven("G");
IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless();
Group g = new Group();
g.addIdentifier().setSystem("urn:foo").setValue("bar");
g.addMember().getEntity().setReferenceElement(pid);
myGroupDao.create(g);
assertThat(myResourceLinkDao.findAll(), not(empty()));
assertThat(ListUtil.filter(myResourceIndexedSearchParamTokenDao.findAll(), new ListUtil.Filter<ResourceIndexedSearchParamToken>() {
@Override
public boolean isOut(ResourceIndexedSearchParamToken object) {
return !object.getResourceType().equals("Group") || object.isMissing();
}
}), not(empty()));
try {
mySearchParameterDao.create(memberSp, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Can not override built-in search parameter Group:member because overriding is disabled on this server", e.getMessage());
}
}
@Test

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
@ -983,10 +984,10 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
List<ResourceIndexedSearchParamNumber> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
ResourceIndexedSearchParamNumber expect0 = new ResourceIndexedSearchParamNumber("RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("1.00"));
ResourceIndexedSearchParamNumber expect0 = new ResourceIndexedSearchParamNumber(new PartitionConfig(), "RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("1.00"));
expect0.setResource(resource);
expect0.calculateHashes();
ResourceIndexedSearchParamNumber expect1 = new ResourceIndexedSearchParamNumber("RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("2.00"));
ResourceIndexedSearchParamNumber expect1 = new ResourceIndexedSearchParamNumber(new PartitionConfig(), "RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("2.00"));
expect1.setResource(resource);
expect1.calculateHashes();

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
@ -650,10 +651,10 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
List<ResourceIndexedSearchParamNumber> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
ResourceIndexedSearchParamNumber expect0 = new ResourceIndexedSearchParamNumber("RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("1.00"));
ResourceIndexedSearchParamNumber expect0 = new ResourceIndexedSearchParamNumber(new PartitionConfig(), "RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("1.00"));
expect0.setResource(resource);
expect0.calculateHashes();
ResourceIndexedSearchParamNumber expect1 = new ResourceIndexedSearchParamNumber("RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("2.00"));
ResourceIndexedSearchParamNumber expect1 = new ResourceIndexedSearchParamNumber(new PartitionConfig(), "RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("2.00"));
expect1.setResource(resource);
expect1.calculateHashes();

View File

@ -708,7 +708,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
p2.setId(new IdType("Patient/" + p1id.getIdPart()));
myOrganizationDao.update(p2, mySrd);
fail();
} catch (UnprocessableEntityException e) {
} catch (InvalidRequestException e) {
ourLog.error("Good", e);
}

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.partition.IPartitionConfigSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
@ -12,10 +13,12 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.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.UnprocessableEntityException;
@ -29,6 +32,7 @@ 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.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter;
@ -49,6 +53,7 @@ import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@ -63,7 +68,9 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
private MyInterceptor myPartitionInterceptor;
private LocalDate myPartitionDate;
private LocalDate myPartitionDate2;
private int myPartitionId;
private int myPartitionId2;
private boolean myHaveDroppedForcedIdUniqueConstraint;
@Autowired
private IPartitionConfigSvc myPartitionConfigSvc;
@ -72,7 +79,9 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
public void after() {
myPartitionInterceptor.assertNoRemainingIds();
myDaoConfig.setPartitioningEnabled(new DaoConfig().isPartitioningEnabled());
myPartitionConfig.setIncludePartitionInSearchHashes(new PartitionConfig().isIncludePartitionInSearchHashes());
myPartitionConfig.setPartitioningEnabled(new PartitionConfig().isPartitioningEnabled());
myPartitionConfig.setAllowReferencesAcrossPartitions(new PartitionConfig().isAllowReferencesAcrossPartitions());
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
myInterceptor = null;
@ -90,12 +99,14 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
public void before() throws ServletException {
super.before();
myDaoConfig.setPartitioningEnabled(true);
myPartitionConfig.setPartitioningEnabled(true);
myDaoConfig.setUniqueIndexesEnabled(true);
myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
myPartitionDate = LocalDate.of(2020, Month.JANUARY, 14);
myPartitionId = 3;
myPartitionDate2 = LocalDate.of(2020, Month.JANUARY, 15);
myPartitionId = 1;
myPartitionId2 = 2;
myPartitionInterceptor = new MyInterceptor();
myInterceptorRegistry.registerInterceptor(myPartitionInterceptor);
@ -124,6 +135,155 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
});
}
@Test
public void testCreate_CrossPartitionReference_ByPid_Allowed() {
myPartitionConfig.setAllowReferencesAcrossPartitions(true);
// Create patient in partition 1
addCreatePartition(myPartitionId, myPartitionDate);
Patient patient = new Patient();
patient.setActive(true);
IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
// Create observation in partition 2
addCreatePartition(myPartitionId2, myPartitionDate2);
Observation obs = new Observation();
obs.getSubject().setReference(patientId.getValue());
IIdType obsId = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
runInTransaction(()->{
List<ResourceLink> resLinks = myResourceLinkDao.findAll();
ourLog.info("Resource links:\n{}", resLinks.toString());
assertEquals(2, resLinks.size());
assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid());
assertEquals(patientId.getIdPartAsLong(), resLinks.get(0).getTargetResourcePid());
});
}
@Test
public void testCreate_CrossPartitionReference_ByPid_NotAllowed() {
// Create patient in partition 1
addCreatePartition(myPartitionId, myPartitionDate);
Patient patient = new Patient();
patient.setActive(true);
IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
// Create observation in partition 2
addCreatePartition(myPartitionId2, myPartitionDate2);
Observation obs = new Observation();
obs.getSubject().setReference(patientId.getValue());
try {
myObservationDao.create(obs).getId().toUnqualifiedVersionless();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), startsWith("Resource Patient/" + patientId.getIdPart() + " not found, specified in path: Observation.subject"));
}
}
@Test
public void testCreate_CrossPartitionReference_ByForcedId_Allowed() {
myPartitionConfig.setAllowReferencesAcrossPartitions(true);
// Create patient in partition 1
addCreatePartition(myPartitionId, myPartitionDate);
Patient patient = new Patient();
patient.setId("ONE");
patient.setActive(true);
IIdType patientId = myPatientDao.update(patient).getId().toUnqualifiedVersionless();
// Create observation in partition 2
addCreatePartition(myPartitionId2, myPartitionDate2);
Observation obs = new Observation();
obs.getSubject().setReference(patientId.getValue());
IIdType obsId = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
runInTransaction(()->{
List<ResourceLink> resLinks = myResourceLinkDao.findAll();
ourLog.info("Resource links:\n{}", resLinks.toString());
assertEquals(2, resLinks.size());
assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid());
assertEquals(patientId.getIdPart(), resLinks.get(0).getTargetResourceId());
});
}
@Test
public void testCreate_CrossPartitionReference_ByForcedId_NotAllowed() {
// Create patient in partition 1
addCreatePartition(myPartitionId, myPartitionDate);
Patient patient = new Patient();
patient.setId("ONE");
patient.setActive(true);
IIdType patientId = myPatientDao.update(patient).getId().toUnqualifiedVersionless();
// Create observation in partition 2
addCreatePartition(myPartitionId2, myPartitionDate2);
Observation obs = new Observation();
obs.getSubject().setReference(patientId.getValue());
try {
myObservationDao.create(obs).getId().toUnqualifiedVersionless();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), startsWith("Resource Patient/ONE not found, specified in path: Observation.subject"));
}
}
@Test
public void testCreate_SamePartitionReference_DefaultPartition_ByPid() {
myPartitionConfig.setAllowReferencesAcrossPartitions(true);
// Create patient in partition NULL
addCreateNoPartitionId(myPartitionDate);
Patient patient = new Patient();
patient.setActive(true);
IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
// Create observation in partition NULL
addCreateNoPartitionId(myPartitionDate);
Observation obs = new Observation();
obs.getSubject().setReference(patientId.getValue());
IIdType obsId = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
runInTransaction(()->{
List<ResourceLink> resLinks = myResourceLinkDao.findAll();
ourLog.info("Resource links:\n{}", resLinks.toString());
assertEquals(2, resLinks.size());
assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid());
assertEquals(patientId.getIdPartAsLong(), resLinks.get(0).getTargetResourcePid());
});
}
@Test
public void testCreate_SamePartitionReference_DefaultPartition_ByForcedId() {
myPartitionConfig.setAllowReferencesAcrossPartitions(true);
// Create patient in partition NULL
addCreateNoPartitionId(myPartitionDate);
Patient patient = new Patient();
patient.setId("ONE");
patient.setActive(true);
IIdType patientId = myPatientDao.update(patient).getId().toUnqualifiedVersionless();
// Create observation in partition NULL
addCreateNoPartitionId(myPartitionDate);
Observation obs = new Observation();
obs.getSubject().setReference(patientId.getValue());
IIdType obsId = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
runInTransaction(()->{
List<ResourceLink> resLinks = myResourceLinkDao.findAll();
ourLog.info("Resource links:\n{}", resLinks.toString());
assertEquals(2, resLinks.size());
assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid());
assertEquals(patientId.getIdPart(), resLinks.get(0).getTargetResourceId());
});
}
@Test
public void testCreateSearchParameter_DefaultPartitionWithDate() {
addCreateNoPartitionId(myPartitionDate);
@ -140,7 +300,7 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(id).orElseThrow(IllegalArgumentException::new);
assertEquals(null, resourceTable.getPartitionId().getPartitionId());
assertNull(resourceTable.getPartitionId().getPartitionId());
assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate());
});
}
@ -269,7 +429,7 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
// HFJ_RES_PARAM_PRESENT
List<SearchParamPresent> presents = mySearchParamPresentDao.findAllForResource(resourceTable);
assertEquals(myPartitionId, presents.size());
assertEquals(3, presents.size());
assertEquals(myPartitionId, presents.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myPartitionDate, presents.get(0).getPartitionId().getPartitionDate());
@ -451,8 +611,8 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
@Test
public void testUpdateResourceWithPartition() {
createRequestId();
addCreatePartition(3, LocalDate.of(2020, Month.JANUARY, 14));
addCreatePartition(3, LocalDate.of(2020, Month.JANUARY, 14));
addCreatePartition(myPartitionId, myPartitionDate);
addCreatePartition(myPartitionId, myPartitionDate);
// Create a resource
Patient p = new Patient();
@ -1033,6 +1193,75 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@Test
public void testSearch_StringParam_SearchAllPartitions_IncludePartitionInHashes() {
myPartitionConfig.setIncludePartitionInSearchHashes(true);
addReadPartition(null);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_FAMILY, new StringParam("FAMILY"));
map.setLoadSynchronous(true);
try {
myPatientDao.search(map);
fail();
} catch (PreconditionFailedException e) {
assertEquals("This server is not configured to support search against all partitions", e.getMessage());
}
}
@Test
public void testSearch_StringParam_SearchDefaultPartition_IncludePartitionInHashes() {
myPartitionConfig.setIncludePartitionInSearchHashes(true);
IIdType patientIdNull = createPatient(null, withFamily("FAMILY"));
createPatient(1, withFamily("FAMILY"));
createPatient(2, withFamily("FAMILY"));
addDefaultReadPartition();
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_FAMILY, new StringParam("FAMILY"));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
searchSql = searchSql.toUpperCase();
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@Test
public void testSearch_StringParam_SearchOnePartition_IncludePartitionInHashes() {
myPartitionConfig.setIncludePartitionInSearchHashes(true);
createPatient(null, withFamily("FAMILY"));
IIdType patientId1 = createPatient(1, withFamily("FAMILY"));
createPatient(2, withFamily("FAMILY"));
addReadPartition(1);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_FAMILY, new StringParam("FAMILY"));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@Test
public void testSearch_TagParam_SearchAllPartitions() {
IIdType patientIdNull = createPatient(null, withActiveTrue(), withTag("http://system", "code"));
@ -1185,6 +1414,211 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
public void testSearch_RefParam_TargetPid_SearchOnePartition() {
createUniqueCompositeSp();
IIdType patientId = createPatient(myPartitionId, withBirthdate("2020-01-01"));
IIdType observationId = createObservation(myPartitionId, withSubject(patientId));
addReadPartition(myPartitionId);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID='1'"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH='Observation.subject'"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// Same query, different partition
addReadPartition(2);
myCaptureQueriesListener.clear();
map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
results = myObservationDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.empty());
}
@Test
public void testSearch_RefParam_TargetPid_SearchDefaultPartition() {
createUniqueCompositeSp();
IIdType patientId = createPatient(null, withBirthdate("2020-01-01"));
IIdType observationId = createObservation(null, withSubject(patientId));
addDefaultReadPartition();;
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH='Observation.subject'"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// Same query, different partition
addReadPartition(2);
myCaptureQueriesListener.clear();
map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
results = myObservationDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.empty());
}
@Test
public void testSearch_RefParam_TargetForcedId_SearchOnePartition() {
createUniqueCompositeSp();
IIdType patientId = createPatient(myPartitionId, withId("ONE"), withBirthdate("2020-01-01"));
IIdType observationId = createObservation(myPartitionId, withSubject(patientId));
addReadPartition(myPartitionId);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID='1' "));
assertEquals(1, StringUtils.countMatches(searchSql, "and forcedid0_.RESOURCE_TYPE='Patient'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// Same query, different partition
addReadPartition(2);
myCaptureQueriesListener.clear();
map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
results = myObservationDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.empty());
}
@Test
public void testSearch_RefParam_TargetForcedId_SearchDefaultPartition() {
createUniqueCompositeSp();
IIdType patientId = createPatient(null, withId("ONE"), withBirthdate("2020-01-01"));
IIdType observationId = createObservation(null, withSubject(patientId));
addDefaultReadPartition();;
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.RESOURCE_TYPE='Patient' "));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// Same query, different partition
addReadPartition(2);
myCaptureQueriesListener.clear();
map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
results = myObservationDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.empty());
}
@Test
public void testHistory_Instance_CorrectTenant() {
IIdType id = createPatient(1, withBirthdate("2020-01-01"));
// Update the patient
addCreatePartition(myPartitionId, myPartitionDate);
Patient p = new Patient();
p.setActive(false);
p.setId(id);
myPatientDao.update(p);
addReadPartition(1);
myCaptureQueriesListener.clear();
IBundleProvider results = myPatientDao.history(id, null, null, mySrd);
List<String> ids = toUnqualifiedIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(id.withVersion("2").getValue(), id.withVersion("1").getValue()));
}
@Test
public void testHistory_Instance_WrongTenant() {
IIdType id = createPatient(1, withBirthdate("2020-01-01"));
// Update the patient
addCreatePartition(myPartitionId, myPartitionDate);
Patient p = new Patient();
p.setActive(false);
p.setId(id);
myPatientDao.update(p);
addReadPartition(2);
try {
myPatientDao.history(id, null, null, mySrd);
fail();
} catch (ResourceNotFoundException e) {
// good
}
}
@Test
public void testHistory_Server() {
try {
mySystemDao.history(null, null, mySrd);
fail();
} catch (MethodNotAllowedException e) {
assertEquals("Type- and Server- level history operation not supported on partitioned server", e.getMessage());
}
}
@Test
public void testHistory_Type() {
try {
myPatientDao.history(null, null, mySrd);
fail();
} catch (MethodNotAllowedException e) {
assertEquals("Type- and Server- level history operation not supported on partitioned server", e.getMessage());
}
}
private void createUniqueCompositeSp() {
addCreateNoPartition();
SearchParameter sp = new SearchParameter();
@ -1285,10 +1719,10 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
return t -> t.getBirthDateElement().setValueAsString(theBirthdate);
}
private Consumer<Patient> withId(String theId) {
private <T extends IBaseResource> Consumer<T> withId(String theId) {
return t -> {
assertThat(theId, matchesPattern("[a-zA-Z0-9]+"));
t.setId("Patient/" + theId);
t.setId(theId);
};
}
@ -1296,6 +1730,29 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
return t -> t.getMeta().addTag(theSystem, theCode, theCode);
}
public IIdType createObservation(Integer thePartitionId, Consumer<Observation>... theModifiers) {
if (thePartitionId != null) {
addCreatePartition(thePartitionId, null);
} else {
addCreateNoPartition();
}
Observation observation = new Observation();
for (Consumer<Observation> next : theModifiers) {
next.accept(observation);
}
if (isNotBlank(observation.getId())) {
return myObservationDao.update(observation, mySrd).getId().toUnqualifiedVersionless();
} else {
return myObservationDao.create(observation, mySrd).getId().toUnqualifiedVersionless();
}
}
private Consumer<Observation> withSubject(IIdType theSubject) {
return t -> t.getSubject().setReferenceElement(theSubject.toUnqualifiedVersionless());
}
@Interceptor
public static class MyInterceptor {

View File

@ -9,11 +9,13 @@ import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -25,6 +27,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@ -57,7 +60,7 @@ public class PartitionManagementProviderTest {
@Test
public void testAddPartition() {
when(myPartitionConfigSvc.createPartition(any())).thenAnswer(t -> t.getArgument(0, PartitionEntity.class));
when(myPartitionConfigSvc.createPartition(any())).thenAnswer(createAnswer());
Parameters input = new Parameters();
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123));
@ -81,6 +84,49 @@ public class PartitionManagementProviderTest {
assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue());
}
@Test
public void testUpdatePartition() {
when(myPartitionConfigSvc.updatePartition(any())).thenAnswer(createAnswer());
Parameters input = new Parameters();
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123));
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("PARTITION-123"));
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, new CodeType("a description"));
Parameters response = myClient
.operation()
.onServer()
.named(ProviderConstants.PARTITION_MANAGEMENT_UPDATE_PARTITION)
.withParameters(input)
.encodedXml()
.execute();
ourLog.info("Response:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response));
verify(myPartitionConfigSvc, times(1)).updatePartition(any());
verifyNoMoreInteractions(myPartitionConfigSvc);
assertEquals(123, ((IntegerType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID)).getValue().intValue());
assertEquals("PARTITION-123", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME)).getValue());
assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue());
}
@Test
public void testDeletePartition() {
Parameters input = new Parameters();
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123));
Parameters response = myClient
.operation()
.onServer()
.named(ProviderConstants.PARTITION_MANAGEMENT_DELETE_PARTITION)
.withParameters(input)
.encodedXml()
.execute();
ourLog.info("Response:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response));
verify(myPartitionConfigSvc, times(1)).deletePartition(eq(123));
verifyNoMoreInteractions(myPartitionConfigSvc);
}
@Configuration
public static class MyConfig {
@ -96,4 +142,11 @@ public class PartitionManagementProviderTest {
}
@NotNull
private static Answer<Object> createAnswer() {
return t -> {
return t.getArgument(0, PartitionEntity.class);
};
}
}

View File

@ -167,60 +167,6 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv
}
@Test
public void testConformanceOverrideNotAllowed() {
myModelConfig.setDefaultSearchParamsCanBeOverridden(false);
CapabilityStatement conformance = ourClient
.fetchConformance()
.ofType(CapabilityStatement.class)
.execute();
Map<String, CapabilityStatementRestResourceSearchParamComponent> map = extractSearchParams(conformance, "Patient");
CapabilityStatementRestResourceSearchParamComponent param = map.get("foo");
assertNull(param);
param = map.get("gender");
assertNotNull(param);
// Add a custom search parameter
SearchParameter fooSp = new SearchParameter();
fooSp.addBase("Patient");
fooSp.setCode("foo");
fooSp.setName("foo");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setExpression("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
// Disable an existing parameter
fooSp = new SearchParameter();
fooSp.addBase("Patient");
fooSp.setCode("gender");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("Gender");
fooSp.setExpression("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegistry.forceRefresh();
conformance = ourClient
.capabilities()
.ofType(CapabilityStatement.class)
.execute();
map = extractSearchParams(conformance, "Patient");
param = map.get("foo");
assertEquals("foo", param.getName());
param = map.get("gender");
assertNotNull(param);
}
@Test
public void testCreatingParamMarksCorrectResourcesForReindexing() {

View File

@ -165,6 +165,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
confProvider.setSearchParamRegistry(ourSearchParamRegistry);
myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);

View File

@ -0,0 +1,157 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.util.ProviderConstants;
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
import ca.uhn.fhir.jpa.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.DEFAULT_PERSISTED_PARTITION_NAME;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@SuppressWarnings("Duplicates")
public class MultitenantServerR4Test extends BaseResourceProviderR4Test {
@Autowired
private RequestTenantPartitionInterceptor myRequestTenantPartitionInterceptor;
@Autowired
private PartitionManagementProvider myPartitionManagementProvider;
private CapturingInterceptor myCapturingInterceptor;
private UrlTenantSelectionInterceptor myTenantInterceptor;
@Override
@Before
public void before() throws Exception {
super.before();
myPartitionConfig.setPartitioningEnabled(true);
ourRestServer.registerInterceptor(myRequestTenantPartitionInterceptor);
ourRestServer.registerProvider(myPartitionManagementProvider);
ourRestServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
myCapturingInterceptor = new CapturingInterceptor();
ourClient.getInterceptorService().registerInterceptor(myCapturingInterceptor);
myTenantInterceptor = new UrlTenantSelectionInterceptor();
ourClient.getInterceptorService().registerInterceptor(myTenantInterceptor);
createTenants();
}
@Override
@After
public void after() throws Exception {
super.after();
myPartitionConfig.setPartitioningEnabled(new PartitionConfig().isPartitioningEnabled());
ourRestServer.unregisterInterceptor(myRequestTenantPartitionInterceptor);
ourRestServer.unregisterProvider(myPartitionManagementProvider);
ourRestServer.setTenantIdentificationStrategy(null);
ourClient.getInterceptorService().unregisterAllInterceptors();
}
@Override
protected boolean shouldLogClient() {
return true;
}
@Test
public void testFetchCapabilityStatement() {
myTenantInterceptor.setTenantId("TENANT-A");
CapabilityStatement cs = ourClient.capabilities().ofType(CapabilityStatement.class).execute();
assertEquals("HAPI FHIR Server", cs.getSoftware().getName());
assertEquals(ourServerBase + "/TENANT-A/metadata", myCapturingInterceptor.getLastRequest().getUri());
}
@Test
public void testCreateAndRead() {
myTenantInterceptor.setTenantId("TENANT-A");
Patient patientA = new Patient();
patientA.setActive(true);
IIdType idA = ourClient.create().resource(patientA).execute().getId().toUnqualifiedVersionless();
myTenantInterceptor.setTenantId("TENANT-B");
Patient patientB = new Patient();
patientB.setActive(true);
ourClient.create().resource(patientB).execute();
// Now read back
myTenantInterceptor.setTenantId("TENANT-A");
Patient response = ourClient.read().resource(Patient.class).withId(idA).execute();
assertTrue(response.getActive());
myTenantInterceptor.setTenantId("TENANT-B");
try {
ourClient.read().resource(Patient.class).withId(idA).execute();
fail();
} catch (ResourceNotFoundException e) {
// good
}
}
@Test
public void testCreate_InvalidTenant() {
myTenantInterceptor.setTenantId("TENANT-ZZZ");
Patient patientA = new Patient();
patientA.setActive(true);
try {
ourClient.create().resource(patientA).execute();
fail();
} catch (ResourceNotFoundException e) {
assertThat(e.getMessage(), containsString("Unknown tenant: TENANT-ZZZ"));
}
}
private void createTenants() {
myTenantInterceptor.setTenantId(DEFAULT_PERSISTED_PARTITION_NAME);
ourClient
.operation()
.onServer()
.named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
.withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(1))
.andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-A"))
.execute();
ourClient
.operation()
.onServer()
.named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
.withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(2))
.andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-B"))
.execute();
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -182,61 +182,6 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide
}
@Test
public void testConformanceOverrideNotAllowed() {
myModelConfig.setDefaultSearchParamsCanBeOverridden(false);
CapabilityStatement conformance = ourClient
.fetchConformance()
.ofType(CapabilityStatement.class)
.execute();
Map<String, CapabilityStatementRestResourceSearchParamComponent> map = extractSearchParams(conformance, "Patient");
CapabilityStatementRestResourceSearchParamComponent param = map.get("foo");
assertNull(param);
param = map.get("gender");
assertNotNull(param);
// Add a custom search parameter
SearchParameter fooSp = new SearchParameter();
fooSp.addBase("Patient");
fooSp.setCode("foo");
fooSp.setName("foo");
fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setExpression("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
// Disable an existing parameter
fooSp = new SearchParameter();
fooSp.addBase("Patient");
fooSp.setCode("gender");
fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("Gender");
fooSp.setExpression("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegistry.forceRefresh();
conformance = ourClient
.fetchConformance()
.ofType(CapabilityStatement.class)
.execute();
map = extractSearchParams(conformance, "Patient");
param = map.get("foo");
assertEquals("foo", param.getName());
param = map.get("gender");
assertNotNull(param);
}
@Test
public void testCreatingParamMarksCorrectResourcesForReindexing() {
Patient pat = new Patient();

View File

@ -14,6 +14,8 @@ import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
@ -108,6 +110,8 @@ public class SearchCoordinatorSvcImplTest {
private SearchBuilderFactory mySearchBuilderFactory;
@Mock
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
@Mock
private IRequestPartitionHelperService myPartitionHelperSvc;
@After
public void after() {
@ -131,6 +135,7 @@ public class SearchCoordinatorSvcImplTest {
mySvc.setInterceptorBroadcasterForUnitTest(myInterceptorBroadcaster);
mySvc.setSearchBuilderFactoryForUnitTest(mySearchBuilderFactory);
mySvc.setPersistedJpaBundleProviderFactoryForUnitTest(myPersistedJpaBundleProviderFactory);
mySvc.setRequestPartitionHelperService(myPartitionHelperSvc);
DaoConfig daoConfig = new DaoConfig();
mySvc.setDaoConfigForUnitTest(daoConfig);

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.migrate.taskdef.BaseTableColumnTypeTask;
import ca.uhn.fhir.jpa.migrate.taskdef.CalculateHashesTask;
import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
import ca.uhn.fhir.jpa.migrate.tasks.api.Builder;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.util.VersionEnum;
@ -479,7 +480,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxCoords
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.5")
.setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME")))
);
}
@ -502,7 +503,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxDate
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.10")
.setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME")))
);
}
@ -523,7 +524,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxNumber
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.14")
.setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME")))
);
}
@ -560,9 +561,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxQuantity
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.22")
.setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY_AND_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashUnits(t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_UNITS")))
.addCalculator("HASH_IDENTITY_SYS_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_SYSTEM"), t.getString("SP_UNITS")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY_AND_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashUnits(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_UNITS")))
.addCalculator("HASH_IDENTITY_SYS_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_SYSTEM"), t.getString("SP_UNITS")))
);
}
@ -591,8 +592,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxString
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.28")
.setColumnName("HASH_NORM_PREFIX")
.addCalculator("HASH_NORM_PREFIX", t -> ResourceIndexedSearchParamString.calculateHashNormalized(new ModelConfig(), t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_VALUE_NORMALIZED")))
.addCalculator("HASH_EXACT", t -> ResourceIndexedSearchParamString.calculateHashExact(t.getResourceType(), t.getParamName(), t.getString("SP_VALUE_EXACT")))
.addCalculator("HASH_NORM_PREFIX", t -> ResourceIndexedSearchParamString.calculateHashNormalized(new PartitionConfig(), null, new ModelConfig(), t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_VALUE_NORMALIZED")))
.addCalculator("HASH_EXACT", t -> ResourceIndexedSearchParamString.calculateHashExact(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE_EXACT")))
);
}
@ -639,10 +640,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxToken
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.39")
.setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")))
.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")))
.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")))
.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")))
.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")))
);
}
@ -669,8 +670,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxUri
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.44")
.setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_URI", t -> ResourceIndexedSearchParamUri.calculateHashUri(t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_URI")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_URI", t -> ResourceIndexedSearchParamUri.calculateHashUri(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_URI")))
);
}
@ -703,7 +704,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
Boolean present = columnToBoolean(t.get("SP_PRESENT"));
String resType = (String) t.get("RES_TYPE");
String paramName = (String) t.get("PARAM_NAME");
Long hash = SearchParamPresent.calculateHashPresence(resType, paramName, present);
Long hash = SearchParamPresent.calculateHashPresence(new PartitionConfig(), null, resType, paramName, present);
consolidateSearchParamPresenceIndexesTask.executeSql("HFJ_RES_PARAM_PRESENT", "update HFJ_RES_PARAM_PRESENT set HASH_PRESENCE = ? where PID = ?", hash, pid);
});
version.addTask(consolidateSearchParamPresenceIndexesTask);

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.migrate.taskdef;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.util.VersionEnum;
import org.junit.Test;
@ -41,7 +41,7 @@ public class ArbitrarySqlTaskTest extends BaseTest {
Boolean present = (Boolean) t.get("SP_PRESENT");
String resType = (String) t.get("RES_TYPE");
String paramName = (String) t.get("PARAM_NAME");
Long hash = SearchParamPresent.calculateHashPresence(resType, paramName, present);
Long hash = SearchParamPresent.calculateHashPresence(new PartitionConfig(), null, resType, paramName, present);
task.executeSql("HFJ_RES_PARAM_PRESENT", "update HFJ_RES_PARAM_PRESENT set HASH_PRESENT = ? where PID = ?", hash, pid);
});

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.migrate.taskdef;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.util.VersionEnum;
@ -26,10 +27,10 @@ public class CalculateHashesTest extends BaseTest {
CalculateHashesTask task = new CalculateHashesTask(VersionEnum.V3_5_0, "1");
task.setTableName("HFJ_SPIDX_TOKEN");
task.setColumnName("HASH_IDENTITY");
task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")));
task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")));
task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")));
task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")));
task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME")));
task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")));
task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")));
task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")));
task.setBatchSize(1);
getMigrator().addTask(task);
@ -73,10 +74,10 @@ public class CalculateHashesTest extends BaseTest {
CalculateHashesTask task = new CalculateHashesTask(VersionEnum.V3_5_0, "1");
task.setTableName("HFJ_SPIDX_TOKEN");
task.setColumnName("HASH_IDENTITY");
task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")));
task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")));
task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")));
task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")));
task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionConfig(), null, t.getResourceType(), t.getString("SP_NAME")));
task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")));
task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")));
task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionConfig(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")));
task.setBatchSize(3);
getMigrator().addTask(task);

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.jpa.model.config;
/**
* @since 5.0.0
*/
public class PartitionConfig {
private boolean myPartitioningEnabled = true;
private boolean myAllowReferencesAcrossPartitions;
private boolean myIncludePartitionInSearchHashes;
/**
* If set to <code>true</code> (default is <code>true</code>) the <code>PARTITION_ID</code> value will be factored into the
* hash values used in the <code>HFJ_SPIDX_xxx</code> tables, removing the need to explicitly add a selector
* on this column in queries. If set to <code>false</code>, an additional selector is used instead, which may perform
* better when using native database partitioning features.
* <p>
* This setting has no effect if partitioning is not enabled via {@link #isPartitioningEnabled()}.
* </p>
*/
public boolean isIncludePartitionInSearchHashes() {
return myIncludePartitionInSearchHashes;
}
/**
* If set to <code>true</code> (default is <code>true</code>) the <code>PARTITION_ID</code> value will be factored into the
* hash values used in the <code>HFJ_SPIDX_xxx</code> tables, removing the need to explicitly add a selector
* on this column in queries. If set to <code>false</code>, an additional selector is used instead, which may perform
* better when using native database partitioning features.
* <p>
* This setting has no effect if partitioning is not enabled via {@link #isPartitioningEnabled()}.
* </p>
*/
public void setIncludePartitionInSearchHashes(boolean theIncludePartitionInSearchHashes) {
myIncludePartitionInSearchHashes = theIncludePartitionInSearchHashes;
}
/**
* If enabled (default is <code>false</code>) the JPA server will support data partitioning
*
* @since 5.0.0
*/
public boolean isPartitioningEnabled() {
return myPartitioningEnabled;
}
/**
* If enabled (default is <code>false</code>) the JPA server will support data partitioning
*
* @since 5.0.0
*/
public void setPartitioningEnabled(boolean theMultiTenancyEnabled) {
myPartitioningEnabled = theMultiTenancyEnabled;
}
/**
* Should resources references be permitted to cross partition boundaries. Default is <code>false</code>.
*
* @since 5.0.0
*/
public boolean isAllowReferencesAcrossPartitions() {
return myAllowReferencesAcrossPartitions;
}
/**
* Should resources references be permitted to cross partition boundaries. Default is <code>false</code>.
*
* @since 5.0.0
*/
public void setAllowReferencesAcrossPartitions(boolean theAllowReferencesAcrossPartitions) {
myAllowReferencesAcrossPartitions = theAllowReferencesAcrossPartitions;
}
}

View File

@ -22,17 +22,21 @@ package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.Constants;
import org.hibernate.annotations.OptimisticLock;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import java.util.Collection;
import java.util.Date;
@MappedSuperclass
public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersistedResource {
public abstract class BaseHasResource extends BasePartitionable implements IBaseResourceEntity, IBasePersistedResource {
@Column(name = "RES_DELETED_AT", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
@ -58,31 +62,13 @@ public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersi
@OptimisticLock(excluded = true)
private Date myUpdated;
@Embedded
private PartitionId myPartitionId;
/**
* This is here to support queries only, do not set this field directly
*/
@SuppressWarnings("unused")
@Column(name = PartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
private Integer myPartitionIdValue;
/**
* This is stored as an optimization to avoid neeind to query for this
* This is stored as an optimization to avoid needing to query for this
* after an update
*/
@Transient
private transient String myTransientForcedId;
public PartitionId getPartitionId() {
return myPartitionId;
}
public void setPartitionId(PartitionId thePartitionId) {
myPartitionId = thePartitionId;
}
public String getTransientForcedId() {
return myTransientForcedId;
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.UrlUtil;
@ -39,6 +40,7 @@ import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import java.util.Date;
@MappedSuperclass
@ -48,6 +50,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
* Don't change this without careful consideration. You will break existing hashes!
*/
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0);
/**
* Don't make this public 'cause nobody better be able to modify it!
*/
@ -80,6 +83,9 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
@Temporal(TemporalType.TIMESTAMP)
private Date myUpdated;
@Transient
private transient PartitionConfig myPartitionConfig;
/**
* Subclasses may override
*/
@ -145,16 +151,30 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
throw new UnsupportedOperationException("No parameter matcher for " + theParam);
}
public static long calculateHashIdentity(String theResourceType, String theParamName) {
return hash(theResourceType, theParamName);
public void setPartitionConfig(PartitionConfig thePartitionConfig) {
myPartitionConfig = thePartitionConfig;
}
public PartitionConfig getPartitionConfig() {
return myPartitionConfig;
}
public static long calculateHashIdentity(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName) {
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName);
}
/**
* Applies a fast and consistent hashing algorithm to a set of strings
*/
static long hash(String... theValues) {
static long hash(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String... theValues) {
Hasher hasher = HASH_FUNCTION.newHasher();
if (thePartitionConfig.isPartitioningEnabled() && thePartitionConfig.isIncludePartitionInSearchHashes() && thePartitionId != null) {
if (thePartitionId.getPartitionId() != null) {
hasher.putInt(thePartitionId.getPartitionId());
}
}
for (String next : theValues) {
if (next == null) {
hasher.putByte((byte) 0);

View File

@ -36,7 +36,7 @@ import javax.persistence.*;
* so don't reuse these names
*/
})
public class ForcedId {
public class ForcedId extends BasePartitionable {
public static final int MAX_FORCED_ID_LENGTH = 100;
public static final String IDX_FORCEDID_TYPE_FID = "IDX_FORCEDID_TYPE_FID";
@ -64,9 +64,6 @@ public class ForcedId {
@Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true)
private String myResourceType;
@Embedded
private PartitionId myPartitionId;
/**
* Constructor
*/
@ -98,11 +95,4 @@ public class ForcedId {
return myId;
}
public PartitionId getPartitionId() {
return myPartitionId;
}
public void setPartitionId(PartitionId thePartitionId) {
myPartitionId = thePartitionId;
}
}

View File

@ -30,7 +30,9 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
// TODO: move this to ca.uhn.fhir.jpa.model.config
public class ModelConfig {
/**
* Default {@link #getTreatReferencesAsLogical() logical URL bases}. Includes the following
* values:
@ -55,7 +57,7 @@ public class ModelConfig {
private boolean myAllowExternalReferences = false;
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<>();
private Set<String> myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS);
private boolean myDefaultSearchParamsCanBeOverridden = false;
private boolean myDefaultSearchParamsCanBeOverridden = true;
private Set<Subscription.SubscriptionChannelType> mySupportedSubscriptionTypes = new HashSet<>();
private String myEmailFromAddress = "noreply@unknown.com";
private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH;
@ -76,7 +78,7 @@ public class ModelConfig {
* the behaviour of the default search parameters.
* </p>
* <p>
* The default value for this setting is {@code false}
* The default value for this setting is {@code true}
* </p>
*/
public boolean isDefaultSearchParamsCanBeOverridden() {
@ -92,7 +94,7 @@ public class ModelConfig {
* the behaviour of the default search parameters.
* </p>
* <p>
* The default value for this setting is {@code false}
* The default value for this setting is {@code true}
* </p>
*/
public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) {

View File

@ -29,7 +29,7 @@ import javax.persistence.*;
@Index(name = "IDX_RESVERPROV_REQUESTID", columnList = "REQUEST_ID")
})
@Entity
public class ResourceHistoryProvenanceEntity {
public class ResourceHistoryProvenanceEntity extends BasePartitionable {
public static final int SOURCE_URI_LENGTH = 100;
@ -47,9 +47,6 @@ public class ResourceHistoryProvenanceEntity {
private String mySourceUri;
@Column(name = "REQUEST_ID", length = Constants.REQUEST_ID_LENGTH, nullable = true)
private String myRequestId;
// FIXME: make sure this gets populated
@Embedded
private PartitionId myPartitionId;
/**
* Constructor
@ -86,11 +83,4 @@ public class ResourceHistoryProvenanceEntity {
return myId;
}
public PartitionId getPartitionId() {
return myPartitionId;
}
public void setPartitionId(PartitionId thePartitionId) {
myPartitionId = thePartitionId;
}
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.model.api.IQueryParameterType;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
@ -61,7 +62,8 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
public ResourceIndexedSearchParamCoords() {
}
public ResourceIndexedSearchParamCoords(String theResourceType, String theParamName, double theLatitude, double theLongitude) {
public ResourceIndexedSearchParamCoords(PartitionConfig thePartitionConfig, String theResourceType, String theParamName, double theLatitude, double theLongitude) {
setPartitionConfig(thePartitionConfig);
setResourceType(theResourceType);
setParamName(theParamName);
setLatitude(theLatitude);
@ -74,7 +76,7 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
if (myHashIdentity == null) {
String resourceType = getResourceType();
String paramName = getParamName();
setHashIdentity(calculateHashIdentity(resourceType, paramName));
setHashIdentity(calculateHashIdentity(getPartitionConfig(), getPartitionId(), resourceType, paramName));
}
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
@ -77,7 +78,8 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
/**
* Constructor
*/
public ResourceIndexedSearchParamDate(String theResourceType, String theParamName, Date theLow, Date theHigh, String theOriginalValue) {
public ResourceIndexedSearchParamDate(PartitionConfig thePartitionConfig, String theResourceType, String theParamName, Date theLow, Date theHigh, String theOriginalValue) {
setPartitionConfig(thePartitionConfig);
setResourceType(theResourceType);
setParamName(theParamName);
setValueLow(theLow);
@ -91,7 +93,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
if (myHashIdentity == null) {
String resourceType = getResourceType();
String paramName = getParamName();
setHashIdentity(calculateHashIdentity(resourceType, paramName));
setHashIdentity(calculateHashIdentity(getPartitionConfig(), getPartitionId(), resourceType, paramName));
}
}
@ -205,6 +207,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
return false;
}
// FIXME: below is always true
boolean result = true;
if (lowerBound != null) {
result &= (myValueLow.after(lowerBound) || myValueLow.equals(lowerBound));

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.util.BigDecimalNumericFieldBridge;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.NumberParam;
@ -65,7 +66,8 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
public ResourceIndexedSearchParamNumber() {
}
public ResourceIndexedSearchParamNumber(String theResourceType, String theParamName, BigDecimal theValue) {
public ResourceIndexedSearchParamNumber(PartitionConfig thePartitionConfig, String theResourceType, String theParamName, BigDecimal theValue) {
setPartitionConfig(thePartitionConfig);
setResourceType(theResourceType);
setParamName(theParamName);
setValue(theValue);
@ -77,7 +79,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
if (myHashIdentity == null) {
String resourceType = getResourceType();
String paramName = getParamName();
setHashIdentity(calculateHashIdentity(resourceType, paramName));
setHashIdentity(calculateHashIdentity(getPartitionConfig(), getPartitionId(), resourceType, paramName));
}
}
@ -106,11 +108,6 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
return b.isEquals();
}
public Long getHashIdentity() {
calculateHashes();
return myHashIdentity;
}
public void setHashIdentity(Long theHashIdentity) {
myHashIdentity = theHashIdentity;
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.util.BigDecimalNumericFieldBridge;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
@ -91,8 +92,9 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
public ResourceIndexedSearchParamQuantity(String theResourceType, String theParamName, BigDecimal theValue, String theSystem, String theUnits) {
public ResourceIndexedSearchParamQuantity(PartitionConfig thePartitionConfig, String theResourceType, String theParamName, BigDecimal theValue, String theSystem, String theUnits) {
this();
setPartitionConfig(thePartitionConfig);
setResourceType(theResourceType);
setParamName(theParamName);
setSystem(theSystem);
@ -108,9 +110,9 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
String paramName = getParamName();
String units = getUnits();
String system = getSystem();
setHashIdentity(calculateHashIdentity(resourceType, paramName));
setHashIdentityAndUnits(calculateHashUnits(resourceType, paramName, units));
setHashIdentitySystemAndUnits(calculateHashSystemAndUnits(resourceType, paramName, system, units));
setHashIdentity(calculateHashIdentity(getPartitionConfig(), getPartitionId(), resourceType, paramName));
setHashIdentityAndUnits(calculateHashUnits(getPartitionConfig(), getPartitionId(), resourceType, paramName, units));
setHashIdentitySystemAndUnits(calculateHashSystemAndUnits(getPartitionConfig(), getPartitionId(), resourceType, paramName, system, units));
}
}
@ -273,12 +275,12 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return retval;
}
public static long calculateHashSystemAndUnits(String theResourceType, String theParamName, String theSystem, String theUnits) {
return hash(theResourceType, theParamName, theSystem, theUnits);
public static long calculateHashSystemAndUnits(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName, String theSystem, String theUnits) {
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, theSystem, theUnits);
}
public static long calculateHashUnits(String theResourceType, String theParamName, String theUnits) {
return hash(theResourceType, theParamName, theUnits);
public static long calculateHashUnits(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName, String theUnits) {
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, theUnits);
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam;
@ -55,50 +56,6 @@ import static org.apache.commons.lang3.StringUtils.left;
@Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID")
})
@Indexed()
//@AnalyzerDefs({
// @AnalyzerDef(name = "autocompleteEdgeAnalyzer",
// tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params= {
// @Parameter(name="pattern", value="(.*)"),
// @Parameter(name="group", value="1")
// }),
// filters = {
// @TokenFilterDef(factory = LowerCaseFilterFactory.class),
// @TokenFilterDef(factory = StopFilterFactory.class),
// @TokenFilterDef(factory = EdgeNGramFilterFactory.class, params = {
// @Parameter(name = "minGramSize", value = "3"),
// @Parameter(name = "maxGramSize", value = "50")
// }),
// }),
// @AnalyzerDef(name = "autocompletePhoneticAnalyzer",
// tokenizer = @TokenizerDef(factory=StandardTokenizerFactory.class),
// filters = {
// @TokenFilterDef(factory=StandardFilterFactory.class),
// @TokenFilterDef(factory=StopFilterFactory.class),
// @TokenFilterDef(factory=PhoneticFilterFactory.class, params = {
// @Parameter(name="encoder", value="DoubleMetaphone")
// }),
// @TokenFilterDef(factory=SnowballPorterFilterFactory.class, params = {
// @Parameter(name="language", value="English")
// })
// }),
// @AnalyzerDef(name = "autocompleteNGramAnalyzer",
// tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
// filters = {
// @TokenFilterDef(factory = WordDelimiterFilterFactory.class),
// @TokenFilterDef(factory = LowerCaseFilterFactory.class),
// @TokenFilterDef(factory = NGramFilterFactory.class, params = {
// @Parameter(name = "minGramSize", value = "3"),
// @Parameter(name = "maxGramSize", value = "20")
// }),
// }),
// @AnalyzerDef(name = "standardAnalyzer",
// tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
// filters = {
// @TokenFilterDef(factory = LowerCaseFilterFactory.class),
// }) // Def
// }
//)
//@formatter:on
public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam {
/*
@ -151,7 +108,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
super();
}
public ResourceIndexedSearchParamString(ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized, String theValueExact) {
public ResourceIndexedSearchParamString(PartitionConfig thePartitionConfig, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized, String theValueExact) {
setPartitionConfig(thePartitionConfig);
setModelConfig(theModelConfig);
setResourceType(theResourceType);
setParamName(theParamName);
@ -172,9 +130,9 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
String paramName = getParamName();
String valueNormalized = getValueNormalized();
String valueExact = getValueExact();
setHashNormalizedPrefix(calculateHashNormalized(myModelConfig, resourceType, paramName, valueNormalized));
setHashExact(calculateHashExact(resourceType, paramName, valueExact));
setHashIdentity(calculateHashIdentity(resourceType, paramName));
setHashNormalizedPrefix(calculateHashNormalized(getPartitionConfig(), getPartitionId(), myModelConfig, resourceType, paramName, valueNormalized));
setHashExact(calculateHashExact(getPartitionConfig(), getPartitionId(), resourceType, paramName, valueExact));
setHashIdentity(calculateHashIdentity(getPartitionConfig(), getPartitionId(), resourceType, paramName));
}
}
@ -293,11 +251,11 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return b.build();
}
public static long calculateHashExact(String theResourceType, String theParamName, String theValueExact) {
return hash(theResourceType, theParamName, theValueExact);
public static long calculateHashExact(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName, String theValueExact) {
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, theValueExact);
}
public static long calculateHashNormalized(ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) {
public static long calculateHashNormalized(PartitionConfig thePartitionConfig, PartitionId thePartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) {
/*
* If we're not allowing contained searches, we'll add the first
* bit of the normalized value to the hash. This helps to
@ -309,8 +267,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
hashPrefixLength = 0;
}
long hash = hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength));
return hash;
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, left(theValueNormalized, hashPrefixLength));
}
@Override

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.TokenParam;
import org.apache.commons.lang3.StringUtils;
@ -101,8 +102,9 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
/**
* Constructor
*/
public ResourceIndexedSearchParamToken(String theResourceType, String theParamName, String theSystem, String theValue) {
public ResourceIndexedSearchParamToken(PartitionConfig thePartitionConfig, String theResourceType, String theParamName, String theSystem, String theValue) {
super();
setPartitionConfig(thePartitionConfig);
setResourceType(theResourceType);
setParamName(theParamName);
setSystem(theSystem);
@ -117,10 +119,10 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
String paramName = getParamName();
String system = getSystem();
String value = getValue();
setHashIdentity(calculateHashIdentity(resourceType, paramName));
setHashSystem(calculateHashSystem(resourceType, paramName, system));
setHashSystemAndValue(calculateHashSystemAndValue(resourceType, paramName, system, value));
setHashValue(calculateHashValue(resourceType, paramName, value));
setHashIdentity(calculateHashIdentity(getPartitionConfig(), getPartitionId(), resourceType, paramName));
setHashSystem(calculateHashSystem(getPartitionConfig(), getPartitionId(), resourceType, paramName, system));
setHashSystemAndValue(calculateHashSystemAndValue(getPartitionConfig(), getPartitionId(), resourceType, paramName, system, value));
setHashValue(calculateHashValue(getPartitionConfig(), getPartitionId(), resourceType, paramName, value));
}
}
@ -190,7 +192,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@Override
public void setId(Long theId) {
myId =theId;
myId = theId;
}
public String getSystem() {
@ -244,39 +246,39 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return false;
}
TokenParam token = (TokenParam) theParam;
boolean retval = false;
boolean retVal = false;
String valueString = defaultString(getValue());
String tokenValueString = defaultString(token.getValue());
// Only match on system if it wasn't specified
if (token.getSystem() == null || token.getSystem().isEmpty()) {
if (valueString.equalsIgnoreCase(tokenValueString)) {
retval = true;
retVal = true;
}
} else if (tokenValueString == null || tokenValueString.isEmpty()) {
if (token.getSystem().equalsIgnoreCase(getSystem())) {
retval = true;
retVal = true;
}
} else {
if (token.getSystem().equalsIgnoreCase(getSystem()) &&
valueString.equalsIgnoreCase(tokenValueString)) {
retval = true;
retVal = true;
}
}
return retval;
return retVal;
}
public static long calculateHashSystem(String theResourceType, String theParamName, String theSystem) {
return hash(theResourceType, theParamName, trim(theSystem));
public static long calculateHashSystem(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName, String theSystem) {
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, trim(theSystem));
}
public static long calculateHashSystemAndValue(String theResourceType, String theParamName, String theSystem, String theValue) {
return hash(theResourceType, theParamName, defaultString(trim(theSystem)), trim(theValue));
public static long calculateHashSystemAndValue(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName, String theSystem, String theValue) {
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, defaultString(trim(theSystem)), trim(theValue));
}
public static long calculateHashValue(String theResourceType, String theParamName, String theValue) {
public static long calculateHashValue(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName, String theValue) {
String value = trim(theValue);
return hash(theResourceType, theParamName, value);
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, value);
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.UriParam;
import org.apache.commons.lang3.StringUtils;
@ -80,7 +81,8 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
/**
* Constructor
*/
public ResourceIndexedSearchParamUri(String theResourceType, String theParamName, String theUri) {
public ResourceIndexedSearchParamUri(PartitionConfig thePartitionConfig, String theResourceType, String theParamName, String theUri) {
setPartitionConfig(thePartitionConfig);
setResourceType(theResourceType);
setParamName(theParamName);
setUri(theUri);
@ -93,8 +95,8 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
String resourceType = getResourceType();
String paramName = getParamName();
String uri = getUri();
setHashIdentity(calculateHashIdentity(resourceType, paramName));
setHashUri(calculateHashUri(resourceType, paramName, uri));
setHashIdentity(calculateHashIdentity(getPartitionConfig(), getPartitionId(), resourceType, paramName));
setHashUri(calculateHashUri(getPartitionConfig(), getPartitionId(), resourceType, paramName, uri));
}
}
@ -196,8 +198,8 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
return defaultString(getUri()).equalsIgnoreCase(uri.getValueNotNull());
}
public static long calculateHashUri(String theResourceType, String theParamName, String theUri) {
return hash(theResourceType, theParamName, theUri);
public static long calculateHashUri(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName, String theUri) {
return hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, theUri);
}

View File

@ -123,6 +123,10 @@ public class ResourceLink extends BaseResourceIndex {
mySourcePath = theSourcePath;
}
public Long getSourceResourcePid() {
return mySourceResourcePid;
}
public ResourceTable getSourceResource() {
return mySourceResource;
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -52,6 +53,8 @@ public class SearchParamPresent extends BasePartitionable implements Serializabl
private transient String myParamName;
@Column(name = "HASH_PRESENCE")
private Long myHashPresence;
@Transient
private transient PartitionConfig myPartitionConfig;
/**
* Constructor
@ -67,7 +70,7 @@ public class SearchParamPresent extends BasePartitionable implements Serializabl
String resourceType = getResource().getResourceType();
String paramName = getParamName();
boolean present = myPresent;
setHashPresence(calculateHashPresence(resourceType, paramName, present));
setHashPresence(calculateHashPresence(getPartitionConfig(), getPartitionId(), resourceType, paramName, present));
}
}
@ -114,9 +117,17 @@ public class SearchParamPresent extends BasePartitionable implements Serializabl
return b.build();
}
public static long calculateHashPresence(String theResourceType, String theParamName, Boolean thePresent) {
public void setPartitionConfig(PartitionConfig thePartitionConfig) {
myPartitionConfig = thePartitionConfig;
}
public PartitionConfig getPartitionConfig() {
return myPartitionConfig;
}
public static long calculateHashPresence(PartitionConfig thePartitionConfig, PartitionId thePartitionId, String theResourceType, String theParamName, Boolean thePresent) {
String string = thePresent != null ? Boolean.toString(thePresent) : Boolean.toString(false);
return BaseResourceIndexedSearchParam.hash(theResourceType, theParamName, string);
return BaseResourceIndexedSearchParam.hash(thePartitionConfig, thePartitionId, theResourceType, theParamName, string);
}
}

View File

@ -32,9 +32,15 @@ public class ProviderConstants {
/**
* Operation name: update partition
*/
public static final String PARTITION_MANAGEMENT_MODIFY_PARTITION = "partition-management-update-partition";
public static final String PARTITION_MANAGEMENT_UPDATE_PARTITION = "partition-management-update-partition";
/**
* Operation name: update partition
*/
public static final String PARTITION_MANAGEMENT_DELETE_PARTITION = "partition-management-delete-partition";
public static final String PARTITION_MANAGEMENT_PARTITION_ID = "partitionId";
public static final String PARTITION_MANAGEMENT_PARTITION_NAME = "partitionName";
public static final String PARTITION_MANAGEMENT_PARTITION_DESC = "description";
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import org.junit.Before;
import org.junit.Test;
@ -35,8 +36,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsTrueForMatchingNullDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", null, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", null, null, "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@ -45,8 +46,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsTrueForMatchingDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1B, date2B, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", date1B, date2B, "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@ -55,8 +56,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsTrueForMatchingTimeStampsThatMatch() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1B, timestamp2B, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", timestamp1B, timestamp2B, "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@ -67,8 +68,8 @@ public class ResourceIndexedSearchParamDateTest {
// other will be equivalent but will be a java.sql.Timestamp. Equals should work in both directions.
@Test
public void equalsIsTrueForMixedTimestampsAndDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@ -77,8 +78,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date2A, date1A, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", date2A, date1A, "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
@ -87,8 +88,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingDatesNullCase() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", null, null, "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
@ -97,8 +98,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingTimeStamps() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, timestamp1A, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", timestamp2A, timestamp1A, "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
@ -107,8 +108,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForMixedTimestampsAndDatesThatDoNotMatch() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, timestamp1A, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionConfig(), "Patient", "SomeResource", timestamp2A, timestamp1A, "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import org.junit.Test;
import java.math.BigDecimal;
@ -10,7 +11,7 @@ import static org.junit.Assert.assertNotEquals;
public class ResourceIndexedSearchParamQuantityTest {
private ResourceIndexedSearchParamQuantity createParam(String theParamName, String theValue, String theSystem, String theUnits) {
ResourceIndexedSearchParamQuantity token = new ResourceIndexedSearchParamQuantity("Patient", theParamName, new BigDecimal(theValue), theSystem, theUnits);
ResourceIndexedSearchParamQuantity token = new ResourceIndexedSearchParamQuantity(new PartitionConfig(), "Patient", theParamName, new BigDecimal(theValue), theSystem, theUnits);
token.setResource(new ResourceTable().setResourceType("Patient"));
return token;
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@ -10,7 +11,7 @@ public class ResourceIndexedSearchParamStringTest {
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new ModelConfig(), "Patient", "NAME", "value", "VALUE");
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new PartitionConfig(), new ModelConfig(), "Patient", "NAME", "value", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results
@ -20,7 +21,7 @@ public class ResourceIndexedSearchParamStringTest {
@Test
public void testHashFunctionsPrefixOnly() {
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new ModelConfig(), "Patient", "NAME", "vZZZZZZZZZZZZZZZZ", "VZZZZZZzzzZzzzZ");
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new PartitionConfig(), new ModelConfig(), "Patient", "NAME", "vZZZZZZZZZZZZZZZZ", "VZZZZZZzzzZzzzZ");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Should be the same as in testHashFunctions()

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@ -9,7 +10,7 @@ public class ResourceIndexedSearchParamTokenTest {
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken("Patient", "NAME", "SYSTEM", "VALUE");
ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken(new PartitionConfig(), "Patient", "NAME", "SYSTEM", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results
@ -20,7 +21,7 @@ public class ResourceIndexedSearchParamTokenTest {
@Test
public void testHashFunctionsWithOverlapNames() {
ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken("Patient", "NAME", "SYSTEM", "VALUE");
ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken(new PartitionConfig(), "Patient", "NAME", "SYSTEM", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@ -9,7 +10,7 @@ public class ResourceIndexedSearchParamUriTest {
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamUri token = new ResourceIndexedSearchParamUri("Patient", "NAME", "http://example.com");
ResourceIndexedSearchParamUri token = new ResourceIndexedSearchParamUri(new PartitionConfig(), "Patient", "NAME", "http://example.com");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
*/
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
@ -59,6 +60,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private ModelConfig myModelConfig;
@Autowired
private PartitionConfig myPartitionConfig;
private Set<String> myIgnoredForSearchDatatypes;
private BaseRuntimeChildDefinition myQuantityValueValueChild;
private BaseRuntimeChildDefinition myQuantitySystemValueChild;
@ -455,7 +458,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String system = extractValueAsString(myQuantitySystemValueChild, theValue);
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(theResourceType, theSearchParam.getName(), nextValueValue, system, code);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionConfig, theResourceType, theSearchParam.getName(), nextValueValue, system, code);
theParams.add(nextEntity);
}
@ -470,7 +473,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
String searchParamName = theSearchParam.getName();
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionConfig, theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode);
theParams.add(nextEntity);
}
@ -547,7 +550,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
Date end = extractValueAsDate(myPeriodEndValueChild, theValue);
if (start != null || end != null) {
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), start, end, startAsString);
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionConfig, theResourceType, theSearchParam.getName(), start, end, startAsString);
theParams.add(nextEntity);
}
}
@ -581,7 +584,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
if (!dates.isEmpty()) {
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), dates.first(), dates.last(), firstValue);
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionConfig, theResourceType, theSearchParam.getName(), dates.first(), dates.last(), firstValue);
theParams.add(nextEntity);
}
}
@ -592,7 +595,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
BigDecimal value = extractValueAsBigDecimal(myDurationValueValueChild, theValue);
if (value != null) {
value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value);
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theResourceType, theSearchParam.getName(), value);
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionConfig, theResourceType, theSearchParam.getName(), value);
theParams.add(nextEntity);
}
}
@ -603,7 +606,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String system = extractValueAsString(myQuantitySystemValueChild, theValue);
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value);
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theResourceType, theSearchParam.getName(), value);
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionConfig, theResourceType, theSearchParam.getName(), value);
theParams.add(nextEntity);
}
}
@ -613,7 +616,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theValue;
if (value.getValue() != null) {
BigDecimal valueDecimal = new BigDecimal(value.getValue());
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theResourceType, theSearchParam.getName(), valueDecimal);
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionConfig, theResourceType, theSearchParam.getName(), valueDecimal);
theParams.add(nextEntity);
}
@ -624,7 +627,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
IPrimitiveType<BigDecimal> value = (IPrimitiveType<BigDecimal>) theValue;
if (value.getValue() != null) {
BigDecimal valueDecimal = value.getValue();
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theResourceType, theSearchParam.getName(), valueDecimal);
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionConfig, theResourceType, theSearchParam.getName(), valueDecimal);
theParams.add(nextEntity);
}
@ -651,7 +654,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
if (latitude != null && longitude != null) {
double normalizedLatitude = Point.normalizeLatitude(latitude.doubleValue());
double normalizedLongitude = Point.normalizeLongitude(longitude.doubleValue());
ResourceIndexedSearchParamCoords nextEntity = new ResourceIndexedSearchParamCoords(theResourceType, theSearchParam.getName(), normalizedLatitude, normalizedLongitude);
ResourceIndexedSearchParamCoords nextEntity = new ResourceIndexedSearchParamCoords(myPartitionConfig, theResourceType, theSearchParam.getName(), normalizedLatitude, normalizedLongitude);
theParams.add(nextEntity);
}
}
@ -772,7 +775,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private void addDateTimeTypes(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<Date> nextBaseDateTime = (IPrimitiveType<Date>) theValue;
if (nextBaseDateTime.getValue() != null) {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString());
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(myPartitionConfig, theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString());
theParams.add(param);
}
}
@ -782,7 +785,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
IPrimitiveType<?> value = (IPrimitiveType<?>) theValue;
String valueAsString = value.getValueAsString();
if (isNotBlank(valueAsString)) {
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(theResourceType, theSearchParam.getName(), valueAsString);
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(myPartitionConfig, theResourceType, theSearchParam.getName(), valueAsString);
theParams.add(nextEntity);
}
}
@ -801,7 +804,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
valueNormalized = valueNormalized.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), theResourceType, searchParamName, valueNormalized, value);
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(myPartitionConfig, getModelConfig(), theResourceType, searchParamName, valueNormalized, value);
Set params = theParams;
params.add(nextEntity);
@ -820,7 +823,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
ResourceIndexedSearchParamToken nextEntity;
nextEntity = new ResourceIndexedSearchParamToken(theResourceType, theSearchParam.getName(), system, value);
nextEntity = new ResourceIndexedSearchParamToken(myPartitionConfig, theResourceType, theSearchParam.getName(), system, value);
theParams.add(nextEntity);
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -36,6 +37,7 @@ public interface IResourceLinkResolver {
* <p>
* This method returns an {@link IResourceLookup} so as to avoid needing to resolve the entire resource.
*
* @param thePartitionId The partition ID of the target resource
* @param theSearchParam The param that is being indexed
* @param theSourcePath The path within the resource where this reference was found
* @param theSourceResourceId The ID of the resource containing the reference to the target being resolved
@ -44,7 +46,7 @@ public interface IResourceLinkResolver {
* @param theReference The reference being resolved
* @param theRequest The incoming request, if any
*/
IResourceLookup findTargetResource(RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest);
IResourceLookup findTargetResource(PartitionId thePartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest);
void validateTypeOrThrowException(Class<? extends IBaseResource> theType);

View File

@ -21,23 +21,28 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
*/
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.ReferenceParam;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.DependsOn;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Predicate;
import static org.apache.commons.lang3.StringUtils.compare;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public final class ResourceIndexedSearchParams {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class);
final public Collection<ResourceIndexedSearchParamString> myStringParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamToken> myTokenParams = new HashSet<>();
final public Collection<ResourceIndexedSearchParamNumber> myNumberParams = new ArrayList<>();
@ -85,7 +90,6 @@ public final class ResourceIndexedSearchParams {
}
public Collection<ResourceLink> getResourceLinks() {
return myLinks;
}
@ -126,6 +130,186 @@ public final class ResourceIndexedSearchParams {
}
}
public void calculateHashes(Collection<? extends BaseResourceIndex> theStringParams) {
for (BaseResourceIndex next : theStringParams) {
next.calculateHashes();
}
}
public Set<String> getPopulatedResourceLinkParameters() {
return myPopulatedResourceLinkParameters;
}
public boolean matchParam(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) {
if (theParamDef == null) {
return false;
}
Collection<? extends BaseResourceIndexedSearchParam> resourceParams;
switch (theParamDef.getParamType()) {
case TOKEN:
resourceParams = myTokenParams;
break;
case QUANTITY:
resourceParams = myQuantityParams;
break;
case STRING:
resourceParams = myStringParams;
break;
case NUMBER:
resourceParams = myNumberParams;
break;
case URI:
resourceParams = myUriParams;
break;
case DATE:
resourceParams = myDateParams;
break;
case REFERENCE:
return matchResourceLinks(theModelConfig, theResourceName, theParamName, theParam, theParamDef.getPath());
case COMPOSITE:
case HAS:
case SPECIAL:
default:
resourceParams = null;
}
if (resourceParams == null) {
return false;
}
Predicate<BaseResourceIndexedSearchParam> namedParamPredicate = param ->
param.getParamName().equalsIgnoreCase(theParamName) &&
param.matches(theParam);
return resourceParams.stream().anyMatch(namedParamPredicate);
}
/**
* @deprecated Replace with the method below
*/
// KHS This needs to be public as libraries outside of hapi call it directly
@Deprecated
public boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) {
return matchResourceLinks(new ModelConfig(), theResourceName, theParamName, theParam, theParamPath);
}
// KHS This needs to be public as libraries outside of hapi call it directly
public boolean matchResourceLinks(ModelConfig theModelConfig, String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) {
ReferenceParam reference = (ReferenceParam) theParam;
Predicate<ResourceLink> namedParamPredicate = resourceLink ->
searchParameterPathMatches(theResourceName, resourceLink, theParamName, theParamPath)
&& resourceIdMatches(theModelConfig, resourceLink, reference);
return myLinks.stream().anyMatch(namedParamPredicate);
}
private boolean resourceIdMatches(ModelConfig theModelConfig, ResourceLink theResourceLink, ReferenceParam theReference) {
String baseUrl = theReference.getBaseUrl();
if (isNotBlank(baseUrl)) {
if (!theModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl)) {
return false;
}
}
String targetType = theResourceLink.getTargetResourceType();
String targetId = theResourceLink.getTargetResourceId();
assert isNotBlank(targetType);
assert isNotBlank(targetId);
if (theReference.hasResourceType()) {
if (!theReference.getResourceType().equals(targetType)) {
return false;
}
}
if (!targetId.equals(theReference.getIdPart())) {
return false;
}
return true;
}
private boolean searchParameterPathMatches(String theResourceName, ResourceLink theResourceLink, String theParamName, String theParamPath) {
String sourcePath = theResourceLink.getSourcePath();
return sourcePath.equalsIgnoreCase(theParamPath);
}
@Override
public String toString() {
return "ResourceIndexedSearchParams{" +
"stringParams=" + myStringParams +
", tokenParams=" + myTokenParams +
", numberParams=" + myNumberParams +
", quantityParams=" + myQuantityParams +
", dateParams=" + myDateParams +
", uriParams=" + myUriParams +
", coordsParams=" + myCoordsParams +
", compositeStringUniques=" + myCompositeStringUniques +
", links=" + myLinks +
'}';
}
public void findMissingSearchParams(PartitionConfig thePartitionConfig, ModelConfig theModelConfig, ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> theActiveSearchParams) {
findMissingSearchParams(thePartitionConfig, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, myStringParams);
findMissingSearchParams(thePartitionConfig, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, myNumberParams);
findMissingSearchParams(thePartitionConfig, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams);
findMissingSearchParams(thePartitionConfig, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, myDateParams);
findMissingSearchParams(thePartitionConfig, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, myUriParams);
findMissingSearchParams(thePartitionConfig, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, myTokenParams);
}
@SuppressWarnings("unchecked")
private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(PartitionConfig thePartitionConfig, ModelConfig theModelConfig, ResourceTable theEntity, Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type,
Collection<RT> paramCollection) {
for (Map.Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
String nextParamName = nextEntry.getKey();
if (nextEntry.getValue().getParamType() == type) {
boolean haveParam = false;
for (BaseResourceIndexedSearchParam nextParam : paramCollection) {
if (nextParam.getParamName().equals(nextParamName)) {
haveParam = true;
break;
}
}
if (!haveParam) {
BaseResourceIndexedSearchParam param;
switch (type) {
case DATE:
param = new ResourceIndexedSearchParamDate();
break;
case NUMBER:
param = new ResourceIndexedSearchParamNumber();
break;
case QUANTITY:
param = new ResourceIndexedSearchParamQuantity();
break;
case STRING:
param = new ResourceIndexedSearchParamString()
.setModelConfig(theModelConfig);
break;
case TOKEN:
param = new ResourceIndexedSearchParamToken();
break;
case URI:
param = new ResourceIndexedSearchParamUri();
break;
case COMPOSITE:
case HAS:
case REFERENCE:
case SPECIAL:
default:
continue;
}
param.setPartitionConfig(thePartitionConfig);
param.setResource(theEntity);
param.setMissing(true);
param.setParamName(nextParamName);
paramCollection.add((RT) param);
}
}
}
}
/**
* This method is used to create a set of all possible combinations of
@ -207,186 +391,4 @@ public final class ResourceIndexedSearchParams {
}
public void calculateHashes(Collection<? extends BaseResourceIndex> theStringParams) {
for (BaseResourceIndex next : theStringParams) {
next.calculateHashes();
}
}
public Set<String> getPopulatedResourceLinkParameters() {
return myPopulatedResourceLinkParameters;
}
public boolean matchParam(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) {
if (theParamDef == null) {
return false;
}
Collection<? extends BaseResourceIndexedSearchParam> resourceParams;
switch (theParamDef.getParamType()) {
case TOKEN:
resourceParams = myTokenParams;
break;
case QUANTITY:
resourceParams = myQuantityParams;
break;
case STRING:
resourceParams = myStringParams;
break;
case NUMBER:
resourceParams = myNumberParams;
break;
case URI:
resourceParams = myUriParams;
break;
case DATE:
resourceParams = myDateParams;
break;
case REFERENCE:
return matchResourceLinks(theModelConfig, theResourceName, theParamName, theParam, theParamDef.getPath());
case COMPOSITE:
case HAS:
case SPECIAL:
default:
resourceParams = null;
}
if (resourceParams == null) {
return false;
}
Predicate<BaseResourceIndexedSearchParam> namedParamPredicate = param ->
param.getParamName().equalsIgnoreCase(theParamName) &&
param.matches(theParam);
return resourceParams.stream().anyMatch(namedParamPredicate);
}
/**
* @deprecated Replace with the method below
*/
// KHS This needs to be public as libraries outside of hapi call it directly
@Deprecated
public boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) {
return matchResourceLinks(new ModelConfig(), theResourceName, theParamName, theParam, theParamPath);
}
// KHS This needs to be public as libraries outside of hapi call it directly
public boolean matchResourceLinks(ModelConfig theModelConfig, String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) {
ReferenceParam reference = (ReferenceParam)theParam;
Predicate<ResourceLink> namedParamPredicate = resourceLink ->
searchParameterPathMatches(theResourceName, resourceLink, theParamName, theParamPath)
&& resourceIdMatches(theModelConfig, resourceLink, reference);
return myLinks.stream().anyMatch(namedParamPredicate);
}
private boolean resourceIdMatches(ModelConfig theModelConfig, ResourceLink theResourceLink, ReferenceParam theReference) {
String baseUrl = theReference.getBaseUrl();
if (isNotBlank(baseUrl)) {
if (!theModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl)) {
return false;
}
}
String targetType = theResourceLink.getTargetResourceType();
String targetId = theResourceLink.getTargetResourceId();
assert isNotBlank(targetType);
assert isNotBlank(targetId);
if (theReference.hasResourceType()) {
if (!theReference.getResourceType().equals(targetType)) {
return false;
}
}
if (!targetId.equals(theReference.getIdPart())) {
return false;
}
return true;
}
private boolean searchParameterPathMatches(String theResourceName, ResourceLink theResourceLink, String theParamName, String theParamPath) {
String sourcePath = theResourceLink.getSourcePath();
return sourcePath.equalsIgnoreCase(theParamPath);
}
@Override
public String toString() {
return "ResourceIndexedSearchParams{" +
"stringParams=" + myStringParams +
", tokenParams=" + myTokenParams +
", numberParams=" + myNumberParams +
", quantityParams=" + myQuantityParams +
", dateParams=" + myDateParams +
", uriParams=" + myUriParams +
", coordsParams=" + myCoordsParams +
", compositeStringUniques=" + myCompositeStringUniques +
", links=" + myLinks +
'}';
}
public void findMissingSearchParams(ModelConfig theModelConfig, ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> theActiveSearchParams) {
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, myStringParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, myNumberParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, myDateParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, myUriParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, myTokenParams);
}
@SuppressWarnings("unchecked")
private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(ModelConfig theModelConfig, ResourceTable theEntity, Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type,
Collection<RT> paramCollection) {
for (Map.Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
String nextParamName = nextEntry.getKey();
if (nextEntry.getValue().getParamType() == type) {
boolean haveParam = false;
for (BaseResourceIndexedSearchParam nextParam : paramCollection) {
if (nextParam.getParamName().equals(nextParamName)) {
haveParam = true;
break;
}
}
if (!haveParam) {
BaseResourceIndexedSearchParam param;
switch (type) {
case DATE:
param = new ResourceIndexedSearchParamDate();
break;
case NUMBER:
param = new ResourceIndexedSearchParamNumber();
break;
case QUANTITY:
param = new ResourceIndexedSearchParamQuantity();
break;
case STRING:
param = new ResourceIndexedSearchParamString()
.setModelConfig(theModelConfig);
break;
case TOKEN:
param = new ResourceIndexedSearchParamToken();
break;
case URI:
param = new ResourceIndexedSearchParamUri();
break;
case COMPOSITE:
case HAS:
case REFERENCE:
case SPECIAL:
default:
continue;
}
param.setResource(theEntity);
param.setMissing(true);
param.setParamName(nextParamName);
paramCollection.add((RT) param);
}
}
}
}
}

View File

@ -24,8 +24,10 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@ -37,7 +39,6 @@ import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
@ -52,6 +53,8 @@ public class ResourceLinkExtractor {
@Autowired
private ModelConfig myModelConfig;
@Autowired
private PartitionConfig myPartitionConfig;
@Autowired
private FhirContext myContext;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@ -174,7 +177,14 @@ public class ResourceLinkExtractor {
IResourceLookup targetResource = theResourceIdToResolvedTarget.get(theNextId.getValue());
if (targetResource == null) {
targetResource = theResourceLinkResolver.findTargetResource(nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest);
// If we're allowing references across partitions, we just don't include the partition ID when resolving the target
PartitionId targetPartitionId = theEntity.getPartitionId();
if (myPartitionConfig.isAllowReferencesAcrossPartitions()) {
targetPartitionId = null;
}
targetResource = theResourceLinkResolver.findTargetResource(targetPartitionId, nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest);
}
if (targetResource == null) {

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.searchparam.matcher;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -34,7 +35,7 @@ import org.springframework.stereotype.Service;
public class InlineResourceLinkResolver implements IResourceLinkResolver {
@Override
public IResourceLookup findTargetResource(RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
public IResourceLookup findTargetResource(PartitionId thePartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
/*
* TODO: JA - This gets used during runtime in-memory matching for subscription. It's not

Some files were not shown because too many files have changed in this diff Show More