Don't merge yet: MegaScale API Changes 2 (#4544)
* Megascale changes 2 * Cleanup * CLeanup * Sync master * Cleanup * Ongoing work * Move service * Fixes * Ongoing megascaler work * Work * Work * Fixes * Work * Test fixes * Work on remote services * Interceptor rework * Work on config refactor * Fixes * Docs tweaks * Address review comments * Test tweak * Test fixes * Try to fix tests * Test tweaks * Fix changelogs * Work on merging * Changes * Megascale fixes * Remove accidental commit * Address fixmes * Cleanup * Test fixes * Compile fix * Test fixes * Account for review comments * Bump HAPI FHIR version
This commit is contained in:
parent
cedf69516b
commit
bf495e2d92
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -628,6 +628,13 @@ public enum Pointcut implements IPointcut {
|
||||||
* This method is called after all processing is completed for a request, but only if the
|
* This method is called after all processing is completed for a request, but only if the
|
||||||
* request completes normally (i.e. no exception is thrown).
|
* request completes normally (i.e. no exception is thrown).
|
||||||
* <p>
|
* <p>
|
||||||
|
* This pointcut is called after the response has completely finished, meaning that the HTTP respsonse to the client
|
||||||
|
* may or may not have already completely been returned to the client by the time this pointcut is invoked. Use caution
|
||||||
|
* if you have timing-dependent logic, since there is no guarantee about whether the client will have already moved on
|
||||||
|
* by the time your method is invoked. If you need a guarantee that your method is invoked before returning to the
|
||||||
|
* client, consider using {@link #SERVER_OUTGOING_RESPONSE} instead.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
* Hooks may accept the following parameters:
|
* Hooks may accept the following parameters:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>
|
* <li>
|
||||||
|
@ -1245,7 +1252,8 @@ public enum Pointcut implements IPointcut {
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>
|
* <li>
|
||||||
* ca.uhn.fhir.rest.server.util.ICachedSearchDetails - Contains the details of the search that
|
* ca.uhn.fhir.rest.server.util.ICachedSearchDetails - Contains the details of the search that
|
||||||
* is being created and initialized
|
* is being created and initialized. Interceptors may use this parameter to modify aspects of the search
|
||||||
|
* before it is stored and executed.
|
||||||
* </li>
|
* </li>
|
||||||
* <li>
|
* <li>
|
||||||
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
|
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
|
||||||
|
@ -1264,6 +1272,9 @@ public enum Pointcut implements IPointcut {
|
||||||
* <li>
|
* <li>
|
||||||
* ca.uhn.fhir.jpa.searchparam.SearchParameterMap - Contains the details of the search being checked. This can be modified.
|
* ca.uhn.fhir.jpa.searchparam.SearchParameterMap - Contains the details of the search being checked. This can be modified.
|
||||||
* </li>
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* ca.uhn.fhir.interceptor.model.RequestPartitionId - The partition associated with the request (or {@literal null} if the server is not partitioned)
|
||||||
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Hooks should return <code>void</code>.
|
* Hooks should return <code>void</code>.
|
||||||
|
@ -1273,7 +1284,8 @@ public enum Pointcut implements IPointcut {
|
||||||
"ca.uhn.fhir.rest.server.util.ICachedSearchDetails",
|
"ca.uhn.fhir.rest.server.util.ICachedSearchDetails",
|
||||||
"ca.uhn.fhir.rest.api.server.RequestDetails",
|
"ca.uhn.fhir.rest.api.server.RequestDetails",
|
||||||
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails",
|
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails",
|
||||||
"ca.uhn.fhir.jpa.searchparam.SearchParameterMap"
|
"ca.uhn.fhir.jpa.searchparam.SearchParameterMap",
|
||||||
|
"ca.uhn.fhir.interceptor.model.RequestPartitionId"
|
||||||
),
|
),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1912,8 +1924,11 @@ public enum Pointcut implements IPointcut {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>Storage Hook:</b>
|
* <b>Storage Hook:</b>
|
||||||
* Invoked before FHIR read/access operation (e.g. <b>read/vread</b>, <b>search</b>, <b>history</b>, etc.) operation to request the
|
* Invoked before any FHIR read/access/extended operation (e.g. <b>read/vread</b>, <b>search</b>, <b>history</b>,
|
||||||
* identification of the partition ID to be associated with the resource(s) being searched for, read, etc.
|
* <b>$reindex</b>, etc.) operation to request the identification of the partition ID to be associated with
|
||||||
|
* the resource(s) being searched for, read, etc. Essentially any operations in the JPA server that are not
|
||||||
|
* creating a resource will use this pointcut. Creates will use {@link #STORAGE_PARTITION_IDENTIFY_CREATE}.
|
||||||
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This hook will only be called if
|
* This hook will only be called if
|
||||||
* partitioning is enabled in the JPA server.
|
* partitioning is enabled in the JPA server.
|
||||||
|
@ -2193,6 +2208,30 @@ public enum Pointcut implements IPointcut {
|
||||||
"ca.uhn.fhir.rest.server.TransactionLogMessages",
|
"ca.uhn.fhir.rest.server.TransactionLogMessages",
|
||||||
"ca.uhn.fhir.mdm.api.MdmLinkEvent"),
|
"ca.uhn.fhir.mdm.api.MdmLinkEvent"),
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>JPA Hook:</b>
|
||||||
|
* This hook is invoked when a cross-partition reference is about to be
|
||||||
|
* stored in the database.
|
||||||
|
* <p>
|
||||||
|
* <b>This is an experimental API - It may change in the future, use with caution.</b>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Hooks may accept the following parameters:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>
|
||||||
|
* {@literal ca.uhn.fhir.jpa.searchparam.extractor.CrossPartitionReferenceDetails} - Contains details about the
|
||||||
|
* cross partition reference.
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Hooks should return <code>void</code>.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
JPA_RESOLVE_CROSS_PARTITION_REFERENCE("ca.uhn.fhir.jpa.model.cross.IResourceLookup",
|
||||||
|
"ca.uhn.fhir.jpa.searchparam.extractor.CrossPartitionReferenceDetails"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>Performance Tracing Hook:</b>
|
* <b>Performance Tracing Hook:</b>
|
||||||
* This hook is invoked when any informational messages generated by the
|
* This hook is invoked when any informational messages generated by the
|
||||||
|
|
|
@ -137,12 +137,12 @@ public class RequestPartitionId implements IModelJson {
|
||||||
|
|
||||||
RequestPartitionId that = (RequestPartitionId) theO;
|
RequestPartitionId that = (RequestPartitionId) theO;
|
||||||
|
|
||||||
return new EqualsBuilder()
|
EqualsBuilder b = new EqualsBuilder();
|
||||||
.append(myAllPartitions, that.myAllPartitions)
|
b.append(myAllPartitions, that.myAllPartitions);
|
||||||
.append(myPartitionDate, that.myPartitionDate)
|
b.append(myPartitionDate, that.myPartitionDate);
|
||||||
.append(myPartitionIds, that.myPartitionIds)
|
b.append(myPartitionIds, that.myPartitionIds);
|
||||||
.append(myPartitionNames, that.myPartitionNames)
|
b.append(myPartitionNames, that.myPartitionNames);
|
||||||
.isEquals();
|
return b.isEquals();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -307,6 +307,10 @@ public class Constants {
|
||||||
public static final String PARAMQUALIFIER_TOKEN_NOT_IN = ":not-in";
|
public static final String PARAMQUALIFIER_TOKEN_NOT_IN = ":not-in";
|
||||||
public static final String PARAMQUALIFIER_TOKEN_ABOVE = ":above";
|
public static final String PARAMQUALIFIER_TOKEN_ABOVE = ":above";
|
||||||
public static final String PARAMQUALIFIER_TOKEN_BELOW = ":below";
|
public static final String PARAMQUALIFIER_TOKEN_BELOW = ":below";
|
||||||
|
/**
|
||||||
|
* The number of characters in a UUID (36)
|
||||||
|
*/
|
||||||
|
public static final int UUID_LENGTH = 36;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-bom</artifactId>
|
<artifactId>hapi-fhir-bom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>HAPI FHIR BOM</name>
|
<name>HAPI FHIR BOM</name>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -105,8 +105,8 @@
|
||||||
<artifactId>mariadb-java-client</artifactId>
|
<artifactId>mariadb-java-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-cli</artifactId>
|
<artifactId>hapi-fhir-cli</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../../hapi-deployable-pom</relativePath>
|
<relativePath>../../hapi-deployable-pom</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -197,43 +197,49 @@ public class LoggingInterceptor implements IClientInterceptor {
|
||||||
/**
|
/**
|
||||||
* Should a summary (one line) for each request be logged, containing the URL and other information
|
* Should a summary (one line) for each request be logged, containing the URL and other information
|
||||||
*/
|
*/
|
||||||
public void setLogRequestBody(boolean theValue) {
|
public LoggingInterceptor setLogRequestBody(boolean theValue) {
|
||||||
myLogRequestBody = theValue;
|
myLogRequestBody = theValue;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should headers for each request be logged, containing the URL and other information
|
* Should headers for each request be logged, containing the URL and other information
|
||||||
*/
|
*/
|
||||||
public void setLogRequestHeaders(boolean theValue) {
|
public LoggingInterceptor setLogRequestHeaders(boolean theValue) {
|
||||||
myLogRequestHeaders = theValue;
|
myLogRequestHeaders = theValue;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should a summary (one line) for each request be logged, containing the URL and other information
|
* Should a summary (one line) for each request be logged, containing the URL and other information
|
||||||
*/
|
*/
|
||||||
public void setLogRequestSummary(boolean theValue) {
|
public LoggingInterceptor setLogRequestSummary(boolean theValue) {
|
||||||
myLogRequestSummary = theValue;
|
myLogRequestSummary = theValue;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should a summary (one line) for each request be logged, containing the URL and other information
|
* Should a summary (one line) for each request be logged, containing the URL and other information
|
||||||
*/
|
*/
|
||||||
public void setLogResponseBody(boolean theValue) {
|
public LoggingInterceptor setLogResponseBody(boolean theValue) {
|
||||||
myLogResponseBody = theValue;
|
myLogResponseBody = theValue;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should headers for each request be logged, containing the URL and other information
|
* Should headers for each request be logged, containing the URL and other information
|
||||||
*/
|
*/
|
||||||
public void setLogResponseHeaders(boolean theValue) {
|
public LoggingInterceptor setLogResponseHeaders(boolean theValue) {
|
||||||
myLogResponseHeaders = theValue;
|
myLogResponseHeaders = theValue;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should a summary (one line) for each request be logged, containing the URL and other information
|
* Should a summary (one line) for each request be logged, containing the URL and other information
|
||||||
*/
|
*/
|
||||||
public void setLogResponseSummary(boolean theValue) {
|
public LoggingInterceptor setLogResponseSummary(boolean theValue) {
|
||||||
myLogResponseSummary = theValue;
|
myLogResponseSummary = theValue;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 4544
|
||||||
|
title: "A new API has been added to the JPA server for storing resources which
|
||||||
|
are actually placeholders for externally stored payloads. This API is not yet
|
||||||
|
used for any published use cases but will be rolled out for future uses."
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 4544
|
||||||
|
title: "The HapiTransactionService (HAPI FHIR JPA Transaction Manager) has been improved
|
||||||
|
and used in a number of new places (replacing the @Transactional annotation). This
|
||||||
|
means that stack traces have much less transaction logic noise, and give a clear
|
||||||
|
indication of transaction boundaries."
|
|
@ -31,6 +31,7 @@ Creating your own interceptors is easy. Custom interceptor classes do not need t
|
||||||
* The method must have an appropriate return value for the chosen [Pointcut](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html).
|
* The method must have an appropriate return value for the chosen [Pointcut](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html).
|
||||||
|
|
||||||
* The method may have any of the parameters specified for the given [Pointcut](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html).
|
* The method may have any of the parameters specified for the given [Pointcut](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html).
|
||||||
|
* A parameter of type [Pointcut](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html) may also be added, and will be passed the actual Pointcut which triggered the invocation.
|
||||||
|
|
||||||
* The method must be public.
|
* The method must be public.
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import ca.uhn.fhir.context.support.IValidationSupport;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
||||||
|
@ -53,6 +54,7 @@ import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||||
import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc;
|
import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
|
import ca.uhn.fhir.jpa.esr.ExternallyStoredResourceServiceRegistry;
|
||||||
import ca.uhn.fhir.jpa.graphql.DaoRegistryGraphQLStorageServices;
|
import ca.uhn.fhir.jpa.graphql.DaoRegistryGraphQLStorageServices;
|
||||||
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
||||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
||||||
|
@ -133,6 +135,7 @@ import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc;
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
|
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
|
||||||
import ca.uhn.fhir.jpa.term.config.TermCodeSystemConfig;
|
import ca.uhn.fhir.jpa.term.config.TermCodeSystemConfig;
|
||||||
|
import ca.uhn.fhir.jpa.util.JpaHapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||||
import ca.uhn.fhir.jpa.validation.ResourceLoaderImpl;
|
import ca.uhn.fhir.jpa.validation.ResourceLoaderImpl;
|
||||||
import ca.uhn.fhir.jpa.validation.ValidationSettings;
|
import ca.uhn.fhir.jpa.validation.ValidationSettings;
|
||||||
|
@ -227,6 +230,11 @@ public class JpaConfig {
|
||||||
return new CascadingDeleteInterceptor(theFhirContext, theDaoRegistry, theInterceptorBroadcaster, threadSafeResourceDeleterSvc);
|
return new CascadingDeleteInterceptor(theFhirContext, theDaoRegistry, theInterceptorBroadcaster, threadSafeResourceDeleterSvc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ExternallyStoredResourceServiceRegistry ExternallyStoredResourceServiceRegistry() {
|
||||||
|
return new ExternallyStoredResourceServiceRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Bean
|
@Bean
|
||||||
public ThreadSafeResourceDeleterSvc safeDeleter(DaoRegistry theDaoRegistry, IInterceptorBroadcaster theInterceptorBroadcaster, HapiTransactionService hapiTransactionService) {
|
public ThreadSafeResourceDeleterSvc safeDeleter(DaoRegistry theDaoRegistry, IInterceptorBroadcaster theInterceptorBroadcaster, HapiTransactionService hapiTransactionService) {
|
||||||
|
@ -383,7 +391,7 @@ public class JpaConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public HapiTransactionService hapiTransactionService() {
|
public HapiTransactionService hapiTransactionService() {
|
||||||
return new HapiTransactionService();
|
return new JpaHapiTransactionService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -524,8 +532,8 @@ public class JpaConfig {
|
||||||
|
|
||||||
@Bean(name = PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER)
|
@Bean(name = PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER)
|
||||||
@Scope("prototype")
|
@Scope("prototype")
|
||||||
public PersistedJpaSearchFirstPageBundleProvider newPersistedJpaSearchFirstPageBundleProvider(RequestDetails theRequest, Search theSearch, SearchTask theSearchTask, ISearchBuilder theSearchBuilder) {
|
public PersistedJpaSearchFirstPageBundleProvider newPersistedJpaSearchFirstPageBundleProvider(RequestDetails theRequest, Search theSearch, SearchTask theSearchTask, ISearchBuilder theSearchBuilder, RequestPartitionId theRequestPartitionId) {
|
||||||
return new PersistedJpaSearchFirstPageBundleProvider(theSearch, theSearchTask, theSearchBuilder, theRequest);
|
return new PersistedJpaSearchFirstPageBundleProvider(theSearch, theSearchTask, theSearchBuilder, theRequest, theRequestPartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = RepositoryValidatingRuleBuilder.REPOSITORY_VALIDATING_RULE_BUILDER)
|
@Bean(name = RepositoryValidatingRuleBuilder.REPOSITORY_VALIDATING_RULE_BUILDER)
|
||||||
|
|
|
@ -30,6 +30,9 @@ import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
|
||||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
import ca.uhn.fhir.jpa.esr.ExternallyStoredResourceAddress;
|
||||||
|
import ca.uhn.fhir.jpa.esr.ExternallyStoredResourceAddressMetadataKey;
|
||||||
|
import ca.uhn.fhir.jpa.esr.ExternallyStoredResourceServiceRegistry;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||||
|
@ -221,8 +224,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
@Autowired
|
@Autowired
|
||||||
protected InMemoryResourceMatcher myInMemoryResourceMatcher;
|
protected InMemoryResourceMatcher myInMemoryResourceMatcher;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected IJpaStorageResourceParser myJpaStorageResourceParser;
|
||||||
|
@Autowired
|
||||||
|
protected PartitionSettings myPartitionSettings;
|
||||||
|
@Autowired
|
||||||
ExpungeService myExpungeService;
|
ExpungeService myExpungeService;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private ExternallyStoredResourceServiceRegistry myExternallyStoredResourceServiceRegistry;
|
||||||
|
@Autowired
|
||||||
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
|
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
|
||||||
|
@ -231,18 +240,18 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
private FhirContext myContext;
|
private FhirContext myContext;
|
||||||
private ApplicationContext myApplicationContext;
|
private ApplicationContext myApplicationContext;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PartitionSettings myPartitionSettings;
|
|
||||||
@Autowired
|
|
||||||
private IPartitionLookupSvc myPartitionLookupSvc;
|
private IPartitionLookupSvc myPartitionLookupSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private MemoryCacheService myMemoryCacheService;
|
private MemoryCacheService myMemoryCacheService;
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private IFulltextSearchSvc myFulltextSearchSvc;
|
private IFulltextSearchSvc myFulltextSearchSvc;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlatformTransactionManager myTransactionManager;
|
private PlatformTransactionManager myTransactionManager;
|
||||||
@Autowired
|
|
||||||
protected IJpaStorageResourceParser myJpaStorageResourceParser;
|
@VisibleForTesting
|
||||||
|
public void setExternallyStoredResourceServiceRegistryForUnitTest(ExternallyStoredResourceServiceRegistry theExternallyStoredResourceServiceRegistry) {
|
||||||
|
myExternallyStoredResourceServiceRegistry = theExternallyStoredResourceServiceRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setSearchParamPresenceSvc(ISearchParamPresenceSvc theSearchParamPresenceSvc) {
|
public void setSearchParamPresenceSvc(ISearchParamPresenceSvc theSearchParamPresenceSvc) {
|
||||||
|
@ -544,6 +553,20 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
|
|
||||||
if (thePerformIndexing) {
|
if (thePerformIndexing) {
|
||||||
|
|
||||||
|
ExternallyStoredResourceAddress address = null;
|
||||||
|
if (myExternallyStoredResourceServiceRegistry.hasProviders()) {
|
||||||
|
address = ExternallyStoredResourceAddressMetadataKey.INSTANCE.get(theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address != null) {
|
||||||
|
|
||||||
|
encoding = ResourceEncodingEnum.ESR;
|
||||||
|
resourceBinary = null;
|
||||||
|
resourceText = address.getProviderId() + ":" + address.getLocation();
|
||||||
|
changed = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
encoding = myStorageSettings.getResourceEncoding();
|
encoding = myStorageSettings.getResourceEncoding();
|
||||||
|
|
||||||
String resourceType = theEntity.getResourceType();
|
String resourceType = theEntity.getResourceType();
|
||||||
|
@ -558,7 +581,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
HashFunction sha256 = Hashing.sha256();
|
HashFunction sha256 = Hashing.sha256();
|
||||||
HashCode hashCode;
|
HashCode hashCode;
|
||||||
String encodedResource = encodeResource(theResource, encoding, excludeElements, myContext);
|
String encodedResource = encodeResource(theResource, encoding, excludeElements, myContext);
|
||||||
if (getStorageSettings().getInlineResourceTextBelowSize() > 0 && encodedResource.length() < getStorageSettings().getInlineResourceTextBelowSize()) {
|
if (myStorageSettings.getInlineResourceTextBelowSize() > 0 && encodedResource.length() < myStorageSettings.getInlineResourceTextBelowSize()) {
|
||||||
resourceText = encodedResource;
|
resourceText = encodedResource;
|
||||||
resourceBinary = null;
|
resourceBinary = null;
|
||||||
encoding = ResourceEncodingEnum.JSON;
|
encoding = ResourceEncodingEnum.JSON;
|
||||||
|
@ -582,6 +605,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
newSourceExtension.setValue(sourceExtension.getValue());
|
newSourceExtension.setValue(sourceExtension.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
encoding = null;
|
encoding = null;
|
||||||
|
@ -662,6 +687,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
case DEL:
|
case DEL:
|
||||||
|
case ESR:
|
||||||
resourceBinary = new byte[0];
|
resourceBinary = new byte[0];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -866,8 +892,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
String toResourceName(IBaseResource theResource) {
|
String toResourceName(IBaseResource theResource) {
|
||||||
return myContext.getResourceType(theResource);
|
return myContext.getResourceType(theResource);
|
||||||
}
|
}
|
||||||
|
@ -1579,6 +1603,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
myPartitionSettings = thePartitionSettings;
|
myPartitionSettings = thePartitionSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not call this method outside of unit tests
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setJpaStorageResourceParserForUnitTest(IJpaStorageResourceParser theJpaStorageResourceParser) {
|
||||||
|
myJpaStorageResourceParser = theJpaStorageResourceParser;
|
||||||
|
}
|
||||||
|
|
||||||
private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization {
|
private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization {
|
||||||
|
|
||||||
private final TagDefinition myTagDefinition;
|
private final TagDefinition myTagDefinition;
|
||||||
|
@ -1629,6 +1661,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
resourceText = GZipUtil.decompress(theResourceBytes);
|
resourceText = GZipUtil.decompress(theResourceBytes);
|
||||||
break;
|
break;
|
||||||
case DEL:
|
case DEL:
|
||||||
|
case ESR:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return resourceText;
|
return resourceText;
|
||||||
|
@ -1688,12 +1721,4 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
||||||
ourValidationDisabledForUnitTest = theValidationDisabledForUnitTest;
|
ourValidationDisabledForUnitTest = theValidationDisabledForUnitTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Do not call this method outside of unit tests
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
public void setJpaStorageResourceParserForUnitTest(IJpaStorageResourceParser theJpaStorageResourceParser) {
|
|
||||||
myJpaStorageResourceParser = theJpaStorageResourceParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseTag;
|
import ca.uhn.fhir.jpa.model.entity.BaseTag;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
|
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
|
||||||
|
@ -99,6 +100,7 @@ import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
|
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.util.ObjectUtil;
|
import ca.uhn.fhir.util.ObjectUtil;
|
||||||
|
@ -147,6 +149,7 @@ import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -242,7 +245,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
|
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
|
||||||
return myTransactionService.execute(theRequestDetails, theTransactionDetails, tx -> doCreateForPost(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails));
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName());
|
||||||
|
return myTransactionService
|
||||||
|
.withRequest(theRequestDetails)
|
||||||
|
.withTransactionDetails(theTransactionDetails)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(tx -> doCreateForPost(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails, requestPartitionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -253,7 +261,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
/**
|
/**
|
||||||
* Called for FHIR create (POST) operations
|
* Called for FHIR create (POST) operations
|
||||||
*/
|
*/
|
||||||
protected DaoMethodOutcome doCreateForPost(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
|
protected DaoMethodOutcome doCreateForPost(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
|
||||||
if (theResource == null) {
|
if (theResource == null) {
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseStorageDao.class, "missingBody");
|
String msg = getContext().getLocalizer().getMessage(BaseStorageDao.class, "missingBody");
|
||||||
throw new InvalidRequestException(Msg.code(956) + msg);
|
throw new InvalidRequestException(Msg.code(956) + msg);
|
||||||
|
@ -274,13 +282,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
theResource.setUserData(JpaConstants.RESOURCE_ID_SERVER_ASSIGNED, Boolean.TRUE);
|
theResource.setUserData(JpaConstants.RESOURCE_ID_SERVER_ASSIGNED, Boolean.TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName());
|
return doCreateForPostOrPut(theRequestDetails, theResource, theIfNoneExist, true, thePerformIndexing, theRequestPartitionId, RestOperationTypeEnum.CREATE, theTransactionDetails);
|
||||||
return doCreateForPostOrPut(theRequestDetails, theResource, theIfNoneExist, true, thePerformIndexing, requestPartitionId, RestOperationTypeEnum.CREATE, theTransactionDetails);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called both for FHIR create (POST) operations (via {@link #doCreateForPost(IBaseResource, String, boolean, TransactionDetails, RequestDetails)}
|
* Called both for FHIR create (POST) operations (via {@link #doCreateForPost(IBaseResource, String, boolean, TransactionDetails, RequestDetails, RequestPartitionId)}
|
||||||
* as well as for FHIR update (PUT) where we're doing a create-with-client-assigned-ID (via {@link #doUpdate(IBaseResource, String, boolean, boolean, RequestDetails, TransactionDetails)}.
|
* as well as for FHIR update (PUT) where we're doing a create-with-client-assigned-ID (via {@link #doUpdate(IBaseResource, String, boolean, boolean, RequestDetails, TransactionDetails, RequestPartitionId)}.
|
||||||
*/
|
*/
|
||||||
private DaoMethodOutcome doCreateForPostOrPut(RequestDetails theRequest, T theResource, String theMatchUrl, boolean theProcessMatchUrl, boolean thePerformIndexing, RequestPartitionId theRequestPartitionId, RestOperationTypeEnum theOperationType, TransactionDetails theTransactionDetails) {
|
private DaoMethodOutcome doCreateForPostOrPut(RequestDetails theRequest, T theResource, String theMatchUrl, boolean theProcessMatchUrl, boolean thePerformIndexing, RequestPartitionId theRequestPartitionId, RestOperationTypeEnum theOperationType, TransactionDetails theTransactionDetails) {
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
|
@ -290,7 +297,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
|
|
||||||
ResourceTable entity = new ResourceTable();
|
ResourceTable entity = new ResourceTable();
|
||||||
entity.setResourceType(toResourceName(theResource));
|
entity.setResourceType(toResourceName(theResource));
|
||||||
entity.setPartitionId(myRequestPartitionHelperService.toStoragePartition(theRequestPartitionId));
|
entity.setPartitionId(PartitionablePartitionId.toStoragePartition(theRequestPartitionId, myPartitionSettings));
|
||||||
entity.setCreatedByMatchUrl(theMatchUrl);
|
entity.setCreatedByMatchUrl(theMatchUrl);
|
||||||
entity.setVersion(1);
|
entity.setVersion(1);
|
||||||
|
|
||||||
|
@ -895,10 +902,16 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) {
|
public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) {
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequestDetails, myResourceName, null, theSince, theUntil, theOffset);
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(myResourceName, null);
|
||||||
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, details);
|
||||||
|
IBundleProvider retVal = myTransactionService
|
||||||
|
.withRequest(theRequestDetails)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
|
return myPersistedJpaBundleProviderFactory.history(theRequestDetails, myResourceName, null, theSince, theUntil, theOffset, requestPartitionId);
|
||||||
|
});
|
||||||
ourLog.debug("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart());
|
ourLog.debug("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart());
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
@ -907,35 +920,48 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
* @deprecated Use {@link #history(IIdType, HistorySearchDateRangeParam, RequestDetails)} instead
|
* @deprecated Use {@link #history(IIdType, HistorySearchDateRangeParam, RequestDetails)} instead
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public IBundleProvider history(final IIdType theId, final Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequest) {
|
public IBundleProvider history(final IIdType theId, final Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequest) {
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
|
|
||||||
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(myResourceName, theId);
|
||||||
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, details);
|
||||||
|
IBundleProvider retVal = myTransactionService
|
||||||
|
.withRequest(theRequest)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
IIdType id = theId.withResourceType(myResourceName).toUnqualifiedVersionless();
|
IIdType id = theId.withResourceType(myResourceName).toUnqualifiedVersionless();
|
||||||
BaseHasResource entity = readEntity(id, theRequest);
|
BaseHasResource entity = readEntity(id, true, theRequest, requestPartitionId);
|
||||||
|
|
||||||
IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequest, myResourceName, entity.getId(), theSince, theUntil, theOffset);
|
return myPersistedJpaBundleProviderFactory.history(theRequest, myResourceName, entity.getId(), theSince, theUntil, theOffset, requestPartitionId);
|
||||||
|
});
|
||||||
|
|
||||||
ourLog.debug("Processed history on {} in {}ms", id, w.getMillisAndRestart());
|
ourLog.debug("Processed history on {} in {}ms", theId, w.getMillisAndRestart());
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public IBundleProvider history(final IIdType theId, final HistorySearchDateRangeParam theHistorySearchDateRangeParam,
|
public IBundleProvider history(final IIdType theId, final HistorySearchDateRangeParam theHistorySearchDateRangeParam,
|
||||||
RequestDetails theRequest) {
|
RequestDetails theRequest) {
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(myResourceName, theId);
|
||||||
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, details);
|
||||||
|
IBundleProvider retVal = myTransactionService
|
||||||
|
.withRequest(theRequest)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
IIdType id = theId.withResourceType(myResourceName).toUnqualifiedVersionless();
|
IIdType id = theId.withResourceType(myResourceName).toUnqualifiedVersionless();
|
||||||
BaseHasResource entity = readEntity(id, theRequest);
|
BaseHasResource entity = readEntity(id, true, theRequest, requestPartitionId);
|
||||||
|
|
||||||
IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequest, myResourceName, entity.getId(),
|
return myPersistedJpaBundleProviderFactory.history(theRequest, myResourceName, entity.getId(),
|
||||||
theHistorySearchDateRangeParam.getLowerBoundAsInstant(),
|
theHistorySearchDateRangeParam.getLowerBoundAsInstant(),
|
||||||
theHistorySearchDateRangeParam.getUpperBoundAsInstant(),
|
theHistorySearchDateRangeParam.getUpperBoundAsInstant(),
|
||||||
theHistorySearchDateRangeParam.getOffset(),
|
theHistorySearchDateRangeParam.getOffset(),
|
||||||
theHistorySearchDateRangeParam.getHistorySearchType());
|
theHistorySearchDateRangeParam.getHistorySearchType(),
|
||||||
|
requestPartitionId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
ourLog.debug("Processed history on {} in {}ms", id, w.getMillisAndRestart());
|
ourLog.debug("Processed history on {} in {}ms", theId, w.getMillisAndRestart());
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -962,8 +988,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
addAllResourcesTypesToReindex(theBase, theRequestDetails, params);
|
addAllResourcesTypesToReindex(theBase, theRequestDetails, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadPartitionIdRequestDetails details = new ReadPartitionIdRequestDetails(null, RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, null, null, null);
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_REINDEX);
|
||||||
RequestPartitionId requestPartition = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, null, details);
|
RequestPartitionId requestPartition = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, details);
|
||||||
params.setRequestPartitionId(requestPartition);
|
params.setRequestPartitionId(requestPartition);
|
||||||
|
|
||||||
JobInstanceStartRequest request = new JobInstanceStartRequest();
|
JobInstanceStartRequest request = new JobInstanceStartRequest();
|
||||||
|
@ -1160,14 +1186,20 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
validateResourceTypeAndThrowInvalidRequestException(theId);
|
||||||
TransactionDetails transactionDetails = new TransactionDetails();
|
TransactionDetails transactionDetails = new TransactionDetails();
|
||||||
|
|
||||||
return myTransactionService.execute(theRequest, transactionDetails, tx -> doRead(theId, theRequest, theDeletedOk));
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForRead(theRequest, myResourceName, theId);
|
||||||
|
|
||||||
|
return myTransactionService
|
||||||
|
.withRequest(theRequest)
|
||||||
|
.withTransactionDetails(transactionDetails)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> doReadInTransaction(theId, theRequest, theDeletedOk, requestPartitionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public T doRead(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) {
|
private T doReadInTransaction(IIdType theId, RequestDetails theRequest, boolean theDeletedOk, RequestPartitionId theRequestPartitionId) {
|
||||||
assert TransactionSynchronizationManager.isActualTransactionActive();
|
assert TransactionSynchronizationManager.isActualTransactionActive();
|
||||||
|
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
BaseHasResource entity = readEntity(theId, theRequest);
|
BaseHasResource entity = readEntity(theId, true, theRequest, theRequestPartitionId);
|
||||||
validateResourceType(entity);
|
validateResourceType(entity);
|
||||||
|
|
||||||
T retVal = myJpaStorageResourceParser.toResource(myResourceType, entity, null, false);
|
T retVal = myJpaStorageResourceParser.toResource(myResourceType, entity, null, false);
|
||||||
|
@ -1214,9 +1246,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
|
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
|
||||||
return readEntity(theId, true, theRequest);
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForRead(theRequest, myResourceName, theId);
|
||||||
|
return myTransactionService
|
||||||
|
.withRequest(theRequest)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> readEntity(theId, true, theRequest, requestPartitionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -1245,13 +1280,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest, RequestPartitionId requestPartitionId) {
|
||||||
@Transactional
|
|
||||||
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
|
||||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
validateResourceTypeAndThrowInvalidRequestException(theId);
|
||||||
|
|
||||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForRead(theRequest, getResourceName(), theId);
|
|
||||||
|
|
||||||
BaseHasResource entity;
|
BaseHasResource entity;
|
||||||
JpaPid pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, getResourceName(), theId.getIdPart());
|
JpaPid pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, getResourceName(), theId.getIdPart());
|
||||||
Set<Integer> readPartitions = null;
|
Set<Integer> readPartitions = null;
|
||||||
|
@ -1361,15 +1392,16 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reindex(T theResource, ResourceTable theEntity) {
|
public void reindex(T theResource, IBasePersistedResource theEntity) {
|
||||||
assert TransactionSynchronizationManager.isActualTransactionActive();
|
assert TransactionSynchronizationManager.isActualTransactionActive();
|
||||||
|
|
||||||
ourLog.debug("Indexing resource {} - PID {}", theEntity.getIdDt().getValue(), theEntity.getId());
|
ourLog.debug("Indexing resource {} - PID {}", theEntity.getIdDt().getValue(), theEntity.getPersistentId());
|
||||||
if (theResource != null) {
|
if (theResource != null) {
|
||||||
CURRENTLY_REINDEXING.put(theResource, Boolean.TRUE);
|
CURRENTLY_REINDEXING.put(theResource, Boolean.TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionDetails transactionDetails = new TransactionDetails(theEntity.getUpdatedDate());
|
ResourceTable entity = (ResourceTable) theEntity;
|
||||||
|
TransactionDetails transactionDetails = new TransactionDetails(entity.getUpdatedDate());
|
||||||
ResourceTable resourceTable = updateEntity(null, theResource, theEntity, theEntity.getDeleted(), true, false, transactionDetails, true, false);
|
ResourceTable resourceTable = updateEntity(null, theResource, theEntity, theEntity.getDeleted(), true, false, transactionDetails, true, false);
|
||||||
if (theResource != null) {
|
if (theResource != null) {
|
||||||
CURRENTLY_REINDEXING.put(theResource, null);
|
CURRENTLY_REINDEXING.put(theResource, null);
|
||||||
|
@ -1450,6 +1482,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
|
|
||||||
if (retVal instanceof PersistedJpaBundleProvider) {
|
if (retVal instanceof PersistedJpaBundleProvider) {
|
||||||
PersistedJpaBundleProvider provider = (PersistedJpaBundleProvider) retVal;
|
PersistedJpaBundleProvider provider = (PersistedJpaBundleProvider) retVal;
|
||||||
|
provider.setRequestPartitionId(requestPartitionId);
|
||||||
if (provider.getCacheStatus() == SearchCacheStatusEnum.HIT) {
|
if (provider.getCacheStatus() == SearchCacheStatusEnum.HIT) {
|
||||||
if (theServletResponse != null && theRequest != null) {
|
if (theServletResponse != null && theRequest != null) {
|
||||||
String value = "HIT from " + theRequest.getFhirServerBase();
|
String value = "HIT from " + theRequest.getFhirServerBase();
|
||||||
|
@ -1520,13 +1553,18 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
@Override
|
@Override
|
||||||
public List<JpaPid> searchForIds(SearchParameterMap theParams, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) {
|
public List<JpaPid> searchForIds(SearchParameterMap theParams, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) {
|
||||||
TransactionDetails transactionDetails = new TransactionDetails();
|
TransactionDetails transactionDetails = new TransactionDetails();
|
||||||
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, myResourceName, theParams, theConditionalOperationTargetOrNull);
|
||||||
|
|
||||||
return myTransactionService.execute(theRequest, transactionDetails, tx -> {
|
return myTransactionService
|
||||||
|
.withRequest(theRequest)
|
||||||
|
.withTransactionDetails(transactionDetails)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
|
|
||||||
if (theParams.getLoadSynchronousUpTo() != null) {
|
if (theParams.getLoadSynchronousUpTo() != null) {
|
||||||
theParams.setLoadSynchronousUpTo(Math.min(getStorageSettings().getInternalSynchronousSearchSize(), theParams.getLoadSynchronousUpTo()));
|
theParams.setLoadSynchronousUpTo(Math.min(myStorageSettings.getInternalSynchronousSearchSize(), theParams.getLoadSynchronousUpTo()));
|
||||||
} else {
|
} else {
|
||||||
theParams.setLoadSynchronousUpTo(getStorageSettings().getInternalSynchronousSearchSize());
|
theParams.setLoadSynchronousUpTo(myStorageSettings.getInternalSynchronousSearchSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
ISearchBuilder builder = mySearchBuilderFactory.newSearchBuilder(this, getResourceName(), getResourceType());
|
ISearchBuilder builder = mySearchBuilderFactory.newSearchBuilder(this, getResourceName(), getResourceType());
|
||||||
|
@ -1534,7 +1572,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
List<JpaPid> ids = new ArrayList<>();
|
List<JpaPid> ids = new ArrayList<>();
|
||||||
|
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams, theConditionalOperationTargetOrNull);
|
|
||||||
|
|
||||||
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
|
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
|
||||||
try (IResultIterator<JpaPid> iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) {
|
try (IResultIterator<JpaPid> iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) {
|
||||||
|
@ -1634,15 +1671,25 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
String id = theResource.getIdElement().getValue();
|
String id = theResource.getIdElement().getValue();
|
||||||
Runnable onRollback = () -> theResource.getIdElement().setValue(id);
|
Runnable onRollback = () -> theResource.getIdElement().setValue(id);
|
||||||
|
|
||||||
// Execute the update in a retryable transaction
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName());
|
||||||
|
|
||||||
|
Callable<DaoMethodOutcome> updateCallback;
|
||||||
if (myStorageSettings.isUpdateWithHistoryRewriteEnabled() && theRequest != null && theRequest.isRewriteHistory()) {
|
if (myStorageSettings.isUpdateWithHistoryRewriteEnabled() && theRequest != null && theRequest.isRewriteHistory()) {
|
||||||
return myTransactionService.execute(theRequest, theTransactionDetails, tx -> doUpdateWithHistoryRewrite(theResource, theRequest, theTransactionDetails), onRollback);
|
updateCallback = () -> doUpdateWithHistoryRewrite(theResource, theRequest, theTransactionDetails, requestPartitionId);
|
||||||
} else {
|
} else {
|
||||||
return myTransactionService.execute(theRequest, theTransactionDetails, tx -> doUpdate(theResource, theMatchUrl, thePerformIndexing, theForceUpdateVersion, theRequest, theTransactionDetails), onRollback);
|
updateCallback = () -> doUpdate(theResource, theMatchUrl, thePerformIndexing, theForceUpdateVersion, theRequest, theTransactionDetails, requestPartitionId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DaoMethodOutcome doUpdate(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
// Execute the update in a retryable transaction
|
||||||
|
return myTransactionService
|
||||||
|
.withRequest(theRequest)
|
||||||
|
.withTransactionDetails(theTransactionDetails)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.onRollback(onRollback)
|
||||||
|
.execute(updateCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DaoMethodOutcome doUpdate(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequest, TransactionDetails theTransactionDetails, RequestPartitionId theRequestPartitionId) {
|
||||||
T resource = theResource;
|
T resource = theResource;
|
||||||
|
|
||||||
preProcessResourceForStorage(resource);
|
preProcessResourceForStorage(resource);
|
||||||
|
@ -1662,8 +1709,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
entity = myEntityManager.find(ResourceTable.class, pid.getId());
|
entity = myEntityManager.find(ResourceTable.class, pid.getId());
|
||||||
resourceId = entity.getIdDt();
|
resourceId = entity.getIdDt();
|
||||||
} else {
|
} else {
|
||||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName());
|
DaoMethodOutcome outcome = doCreateForPostOrPut(theRequest, resource, theMatchUrl, false, thePerformIndexing, theRequestPartitionId, update, theTransactionDetails);
|
||||||
DaoMethodOutcome outcome = doCreateForPostOrPut(theRequest, resource, theMatchUrl, false, thePerformIndexing, requestPartitionId, update, theTransactionDetails);
|
|
||||||
|
|
||||||
// Pre-cache the match URL
|
// Pre-cache the match URL
|
||||||
if (outcome.getPersistentId() != null) {
|
if (outcome.getPersistentId() != null) {
|
||||||
|
@ -1682,8 +1728,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
assert resourceId != null;
|
assert resourceId != null;
|
||||||
assert resourceId.hasIdPart();
|
assert resourceId.hasIdPart();
|
||||||
|
|
||||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName());
|
|
||||||
|
|
||||||
boolean create = false;
|
boolean create = false;
|
||||||
|
|
||||||
if (theRequest != null) {
|
if (theRequest != null) {
|
||||||
|
@ -1695,14 +1739,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
|
|
||||||
if (!create) {
|
if (!create) {
|
||||||
try {
|
try {
|
||||||
entity = readEntityLatestVersion(resourceId, requestPartitionId, theTransactionDetails);
|
entity = readEntityLatestVersion(resourceId, theRequestPartitionId, theTransactionDetails);
|
||||||
} catch (ResourceNotFoundException e) {
|
} catch (ResourceNotFoundException e) {
|
||||||
create = true;
|
create = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (create) {
|
if (create) {
|
||||||
return doCreateForPostOrPut(theRequest, resource, null, false, thePerformIndexing, requestPartitionId, update, theTransactionDetails);
|
return doCreateForPostOrPut(theRequest, resource, null, false, thePerformIndexing, theRequestPartitionId, update, theTransactionDetails);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1720,7 +1764,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
* @param theTransactionDetails details of the transaction
|
* @param theTransactionDetails details of the transaction
|
||||||
* @return the outcome of the operation
|
* @return the outcome of the operation
|
||||||
*/
|
*/
|
||||||
private DaoMethodOutcome doUpdateWithHistoryRewrite(T theResource, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
private DaoMethodOutcome doUpdateWithHistoryRewrite(T theResource, RequestDetails theRequest, TransactionDetails theTransactionDetails, RequestPartitionId theRequestPartitionId) {
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
|
|
||||||
// No need for indexing as this will update a non-current version of the resource which will not be searchable
|
// No need for indexing as this will update a non-current version of the resource which will not be searchable
|
||||||
|
@ -1736,7 +1780,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
assert resourceId.hasIdPart();
|
assert resourceId.hasIdPart();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
currentEntity = readEntityLatestVersion(resourceId.toVersionless(), theRequest, theTransactionDetails);
|
currentEntity = readEntityLatestVersion(resourceId.toVersionless(), theRequestPartitionId, theTransactionDetails);
|
||||||
|
|
||||||
if (!resourceId.hasVersionIdPart()) {
|
if (!resourceId.hasVersionIdPart()) {
|
||||||
throw new InvalidRequestException(Msg.code(2093) + "Invalid resource ID, ID must contain a history version");
|
throw new InvalidRequestException(Msg.code(2093) + "Invalid resource ID, ID must contain a history version");
|
||||||
|
|
|
@ -3,6 +3,8 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
|
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
||||||
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
||||||
|
@ -10,9 +12,12 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
|
||||||
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
|
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
|
||||||
|
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
|
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
|
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
|
||||||
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
|
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.util.QueryChunker;
|
import ca.uhn.fhir.jpa.util.QueryChunker;
|
||||||
|
@ -88,6 +93,10 @@ public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends B
|
||||||
private IResourceTagDao myResourceTagDao;
|
private IResourceTagDao myResourceTagDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
@Autowired
|
||||||
|
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||||
|
@Autowired
|
||||||
|
private IHapiTransactionService myTransactionService;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setTransactionProcessorForUnitTest(TransactionProcessor theTransactionProcessor) {
|
public void setTransactionProcessorForUnitTest(TransactionProcessor theTransactionProcessor) {
|
||||||
|
@ -124,7 +133,6 @@ public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends B
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.SUPPORTS)
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Long> getResourceCountsFromCache() {
|
public Map<String, Long> getResourceCountsFromCache() {
|
||||||
|
@ -138,26 +146,31 @@ public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends B
|
||||||
@Override
|
@Override
|
||||||
public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) {
|
public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) {
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequestDetails, null, null, theSince, theUntil, theOffset);
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(null, null);
|
||||||
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, details);
|
||||||
|
IBundleProvider retVal = myTransactionService
|
||||||
|
.withRequest(theRequestDetails)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> myPersistedJpaBundleProviderFactory.history(theRequestDetails, null, null, theSince, theUntil, theOffset, requestPartitionId));
|
||||||
ourLog.info("Processed global history in {}ms", w.getMillisAndRestart());
|
ourLog.info("Processed global history in {}ms", w.getMillisAndRestart());
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.NEVER)
|
|
||||||
public T transaction(RequestDetails theRequestDetails, T theRequest) {
|
public T transaction(RequestDetails theRequestDetails, T theRequest) {
|
||||||
|
HapiTransactionService.noTransactionAllowed();
|
||||||
return myTransactionProcessor.transaction(theRequestDetails, theRequest, false);
|
return myTransactionProcessor.transaction(theRequestDetails, theRequest, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.MANDATORY)
|
|
||||||
public T transactionNested(RequestDetails theRequestDetails, T theRequest) {
|
public T transactionNested(RequestDetails theRequestDetails, T theRequest) {
|
||||||
|
HapiTransactionService.requireTransaction();
|
||||||
return myTransactionProcessor.transaction(theRequestDetails, theRequest, true);
|
return myTransactionProcessor.transaction(theRequestDetails, theRequest, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.MANDATORY)
|
|
||||||
public <P extends IResourcePersistentId> void preFetchResources(List<P> theResolvedIds) {
|
public <P extends IResourcePersistentId> void preFetchResources(List<P> theResolvedIds) {
|
||||||
|
HapiTransactionService.requireTransaction();
|
||||||
List<Long> pids = theResolvedIds
|
List<Long> pids = theResolvedIds
|
||||||
.stream()
|
.stream()
|
||||||
.map(t -> ((JpaPid) t).getId())
|
.map(t -> ((JpaPid) t).getId())
|
||||||
|
|
|
@ -29,6 +29,8 @@ import ca.uhn.fhir.jpa.api.dao.IDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
|
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
|
||||||
|
import ca.uhn.fhir.jpa.esr.ExternallyStoredResourceServiceRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.esr.IExternallyStoredResourceService;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseTag;
|
import ca.uhn.fhir.jpa.model.entity.BaseTag;
|
||||||
|
@ -87,6 +89,8 @@ public class JpaStorageResourceParser implements IJpaStorageResourceParser {
|
||||||
private PartitionSettings myPartitionSettings;
|
private PartitionSettings myPartitionSettings;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPartitionLookupSvc myPartitionLookupSvc;
|
private IPartitionLookupSvc myPartitionLookupSvc;
|
||||||
|
@Autowired
|
||||||
|
private ExternallyStoredResourceServiceRegistry myExternallyStoredResourceServiceRegistry;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBaseResource toResource(IBasePersistedResource theEntity, boolean theForHistoryOperation) {
|
public IBaseResource toResource(IBasePersistedResource theEntity, boolean theForHistoryOperation) {
|
||||||
|
@ -231,18 +235,29 @@ public class JpaStorageResourceParser implements IJpaStorageResourceParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <R extends IBaseResource> R parseResource(IBaseResourceEntity theEntity, ResourceEncodingEnum resourceEncoding, String decodedResourceText, Class<R> resourceType) {
|
private <R extends IBaseResource> R parseResource(IBaseResourceEntity theEntity, ResourceEncodingEnum theResourceEncoding, String theDecodedResourceText, Class<R> theResourceType) {
|
||||||
R retVal;
|
R retVal;
|
||||||
if (resourceEncoding != ResourceEncodingEnum.DEL) {
|
if (theResourceEncoding == ResourceEncodingEnum.ESR) {
|
||||||
|
|
||||||
|
int colonIndex = theDecodedResourceText.indexOf(':');
|
||||||
|
Validate.isTrue(colonIndex > 0, "Invalid ESR address: %s", theDecodedResourceText);
|
||||||
|
String providerId = theDecodedResourceText.substring(0, colonIndex);
|
||||||
|
String address = theDecodedResourceText.substring(colonIndex + 1);
|
||||||
|
Validate.notBlank(providerId, "No provider ID in ESR address: %s", theDecodedResourceText);
|
||||||
|
Validate.notBlank(address, "No address in ESR address: %s", theDecodedResourceText);
|
||||||
|
IExternallyStoredResourceService provider = myExternallyStoredResourceServiceRegistry.getProvider(providerId);
|
||||||
|
retVal = (R) provider.fetchResource(address);
|
||||||
|
|
||||||
|
} else if (theResourceEncoding != ResourceEncodingEnum.DEL) {
|
||||||
|
|
||||||
IParser parser = new TolerantJsonParser(getContext(theEntity.getFhirVersion()), LENIENT_ERROR_HANDLER, theEntity.getId());
|
IParser parser = new TolerantJsonParser(getContext(theEntity.getFhirVersion()), LENIENT_ERROR_HANDLER, theEntity.getId());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
retVal = parser.parseResource(resourceType, decodedResourceText);
|
retVal = parser.parseResource(theResourceType, theDecodedResourceText);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
b.append("Failed to parse database resource[");
|
b.append("Failed to parse database resource[");
|
||||||
b.append(myFhirContext.getResourceType(resourceType));
|
b.append(myFhirContext.getResourceType(theResourceType));
|
||||||
b.append("/");
|
b.append("/");
|
||||||
b.append(theEntity.getIdDt().getIdPart());
|
b.append(theEntity.getIdDt().getIdPart());
|
||||||
b.append(" (pid ");
|
b.append(" (pid ");
|
||||||
|
|
|
@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.dao.expunge;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
|
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.entity.Batch2JobInstanceEntity;
|
import ca.uhn.fhir.jpa.entity.Batch2JobInstanceEntity;
|
||||||
import ca.uhn.fhir.jpa.entity.Batch2WorkChunkEntity;
|
import ca.uhn.fhir.jpa.entity.Batch2WorkChunkEntity;
|
||||||
|
@ -69,8 +71,10 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
|
||||||
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
|
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
|
||||||
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
|
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
|
@ -96,12 +100,13 @@ public class ExpungeEverythingService implements IExpungeEverythingService {
|
||||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||||
protected EntityManager myEntityManager;
|
protected EntityManager myEntityManager;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
@Autowired
|
||||||
private HapiTransactionService myTxService;
|
private HapiTransactionService myTxService;
|
||||||
@Autowired
|
|
||||||
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MemoryCacheService myMemoryCacheService;
|
private MemoryCacheService myMemoryCacheService;
|
||||||
|
@Autowired
|
||||||
|
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||||
|
|
||||||
private int deletedResourceEntityCount;
|
private int deletedResourceEntityCount;
|
||||||
|
|
||||||
|
@ -118,63 +123,67 @@ public class ExpungeEverythingService implements IExpungeEverythingService {
|
||||||
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING, hooks);
|
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING, hooks);
|
||||||
|
|
||||||
ourLog.info("BEGINNING GLOBAL $expunge");
|
ourLog.info("BEGINNING GLOBAL $expunge");
|
||||||
myTxService.withRequest(theRequest).withPropagation(Propagation.REQUIRES_NEW).execute(()-> {
|
Propagation propagation = Propagation.REQUIRES_NEW;
|
||||||
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_EXPUNGE);
|
||||||
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequest, details);
|
||||||
|
|
||||||
|
myTxService.withRequest(theRequest).withPropagation(propagation).withRequestPartitionId(requestPartitionId).execute(() -> {
|
||||||
counter.addAndGet(doExpungeEverythingQuery("UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null"));
|
counter.addAndGet(doExpungeEverythingQuery("UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null"));
|
||||||
});
|
});
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, Batch2WorkChunkEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, Batch2WorkChunkEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, Batch2JobInstanceEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, Batch2JobInstanceEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, NpmPackageVersionResourceEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, NpmPackageVersionResourceEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, NpmPackageVersionEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, NpmPackageVersionEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, NpmPackageEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, NpmPackageEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SearchParamPresentEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SearchParamPresentEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, BulkImportJobFileEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, BulkImportJobFileEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, BulkImportJobEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, BulkImportJobEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ForcedId.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ForcedId.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamDate.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamDate.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamNumber.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamNumber.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamQuantity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamQuantity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamQuantityNormalized.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamQuantityNormalized.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamString.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamString.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamToken.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamToken.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamUri.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamUri.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamCoords.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedSearchParamCoords.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedComboStringUnique.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedComboStringUnique.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedComboTokenNonUnique.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceIndexedComboTokenNonUnique.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceLink.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceLink.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SearchResult.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SearchResult.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SearchInclude.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SearchInclude.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermValueSetConceptDesignation.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermValueSetConceptDesignation.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermValueSetConcept.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermValueSetConcept.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermValueSet.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermValueSet.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptParentChildLink.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptParentChildLink.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMapGroupElementTarget.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMapGroupElementTarget.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMapGroupElement.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMapGroupElement.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMapGroup.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMapGroup.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMap.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMap.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptProperty.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptProperty.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptDesignation.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptDesignation.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConcept.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConcept.class, requestPartitionId));
|
||||||
myTxService.withRequest(theRequest).withPropagation(Propagation.REQUIRES_NEW).execute(()-> {
|
myTxService.withRequest(theRequest).withPropagation(propagation).withRequestPartitionId(requestPartitionId).execute(() -> {
|
||||||
for (TermCodeSystem next : myEntityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) {
|
for (TermCodeSystem next : myEntityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) {
|
||||||
next.setCurrentVersion(null);
|
next.setCurrentVersion(null);
|
||||||
myEntityManager.merge(next);
|
myEntityManager.merge(next);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermCodeSystemVersion.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermCodeSystemVersion.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermCodeSystem.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermCodeSystem.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SubscriptionTable.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SubscriptionTable.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceHistoryTag.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceHistoryTag.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceTag.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceTag.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TagDefinition.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TagDefinition.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceHistoryProvenanceEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceHistoryProvenanceEntity.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceHistoryTable.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceHistoryTable.class, requestPartitionId));
|
||||||
int counterBefore = counter.get();
|
int counterBefore = counter.get();
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceTable.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceTable.class, requestPartitionId));
|
||||||
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, PartitionEntity.class));
|
counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, PartitionEntity.class, requestPartitionId));
|
||||||
|
|
||||||
deletedResourceEntityCount = counter.get() - counterBefore;
|
deletedResourceEntityCount = counter.get() - counterBefore;
|
||||||
|
|
||||||
myTxService.withRequest(theRequest).withPropagation(Propagation.REQUIRES_NEW).execute(()-> {
|
myTxService.withRequest(theRequest).withPropagation(propagation).withRequestPartitionId(requestPartitionId).execute(() -> {
|
||||||
counter.addAndGet(doExpungeEverythingQuery("DELETE from " + Search.class.getSimpleName() + " d"));
|
counter.addAndGet(doExpungeEverythingQuery("DELETE from " + Search.class.getSimpleName() + " d"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -192,12 +201,12 @@ public class ExpungeEverythingService implements IExpungeEverythingService {
|
||||||
myMemoryCacheService.invalidateAllCaches();
|
myMemoryCacheService.invalidateAllCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int expungeEverythingByTypeWithoutPurging(RequestDetails theRequest, Class<?> theEntityType) {
|
private int expungeEverythingByTypeWithoutPurging(RequestDetails theRequest, Class<?> theEntityType, RequestPartitionId theRequestPartitionId) {
|
||||||
int outcome = 0;
|
int outcome = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
StopWatch sw = new StopWatch();
|
StopWatch sw = new StopWatch();
|
||||||
|
|
||||||
int count = myTxService.withRequest(theRequest).withPropagation(Propagation.REQUIRES_NEW).execute(()-> {
|
int count = myTxService.withRequest(theRequest).withPropagation(Propagation.REQUIRES_NEW).withRequestPartitionId(theRequestPartitionId).execute(() -> {
|
||||||
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
|
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
|
||||||
CriteriaQuery<?> cq = cb.createQuery(theEntityType);
|
CriteriaQuery<?> cq = cb.createQuery(theEntityType);
|
||||||
cq.from(theEntityType);
|
cq.from(theEntityType);
|
||||||
|
@ -215,14 +224,14 @@ public class ExpungeEverythingService implements IExpungeEverythingService {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ourLog.info("Have deleted {} entities of type {} in {}", outcome, theEntityType.getSimpleName(), sw.toString());
|
ourLog.info("Have deleted {} entities of type {} in {}", outcome, theEntityType.getSimpleName(), sw);
|
||||||
}
|
}
|
||||||
return outcome;
|
return outcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int expungeEverythingByType(Class<?> theEntityType) {
|
public int expungeEverythingByType(Class<?> theEntityType) {
|
||||||
int result = expungeEverythingByTypeWithoutPurging(null, theEntityType);
|
int result = expungeEverythingByTypeWithoutPurging(null, theEntityType, RequestPartitionId.allPartitions());
|
||||||
purgeAllCaches();
|
purgeAllCaches();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -235,7 +244,7 @@ public class ExpungeEverythingService implements IExpungeEverythingService {
|
||||||
private int doExpungeEverythingQuery(String theQuery) {
|
private int doExpungeEverythingQuery(String theQuery) {
|
||||||
StopWatch sw = new StopWatch();
|
StopWatch sw = new StopWatch();
|
||||||
int outcome = myEntityManager.createQuery(theQuery).executeUpdate();
|
int outcome = myEntityManager.createQuery(theQuery).executeUpdate();
|
||||||
ourLog.debug("SqlQuery affected {} rows in {}: {}", outcome, sw.toString(), theQuery);
|
ourLog.debug("SqlQuery affected {} rows in {}: {}", outcome, sw, theQuery);
|
||||||
return outcome;
|
return outcome;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.entity;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum;
|
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
import org.hl7.fhir.r5.model.InstantType;
|
import org.hl7.fhir.r5.model.InstantType;
|
||||||
|
@ -46,6 +47,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.rest.api.Constants.UUID_LENGTH;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.left;
|
import static org.apache.commons.lang3.StringUtils.left;
|
||||||
|
|
||||||
|
@ -58,7 +60,7 @@ import static org.apache.commons.lang3.StringUtils.left;
|
||||||
* See the BulkExportAppCtx for job details
|
* See the BulkExportAppCtx for job details
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "HFJ_BLK_EXPORT_JOB", uniqueConstraints = {
|
@Table(name = BulkExportJobEntity.HFJ_BLK_EXPORT_JOB, uniqueConstraints = {
|
||||||
@UniqueConstraint(name = "IDX_BLKEX_JOB_ID", columnNames = "JOB_ID")
|
@UniqueConstraint(name = "IDX_BLKEX_JOB_ID", columnNames = "JOB_ID")
|
||||||
}, indexes = {
|
}, indexes = {
|
||||||
@Index(name = "IDX_BLKEX_EXPTIME", columnList = "EXP_TIME")
|
@Index(name = "IDX_BLKEX_EXPTIME", columnList = "EXP_TIME")
|
||||||
|
@ -68,13 +70,15 @@ public class BulkExportJobEntity implements Serializable {
|
||||||
|
|
||||||
public static final int REQUEST_LENGTH = 1024;
|
public static final int REQUEST_LENGTH = 1024;
|
||||||
public static final int STATUS_MESSAGE_LEN = 500;
|
public static final int STATUS_MESSAGE_LEN = 500;
|
||||||
|
public static final String JOB_ID = "JOB_ID";
|
||||||
|
public static final String HFJ_BLK_EXPORT_JOB = "HFJ_BLK_EXPORT_JOB";
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_BLKEXJOB_PID")
|
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_BLKEXJOB_PID")
|
||||||
@SequenceGenerator(name = "SEQ_BLKEXJOB_PID", sequenceName = "SEQ_BLKEXJOB_PID")
|
@SequenceGenerator(name = "SEQ_BLKEXJOB_PID", sequenceName = "SEQ_BLKEXJOB_PID")
|
||||||
@Column(name = "PID")
|
@Column(name = "PID")
|
||||||
private Long myId;
|
private Long myId;
|
||||||
|
|
||||||
@Column(name = "JOB_ID", length = Search.UUID_COLUMN_LENGTH, nullable = false)
|
@Column(name = JOB_ID, length = UUID_LENGTH, nullable = false)
|
||||||
private String myJobId;
|
private String myJobId;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
|
|
|
@ -40,21 +40,24 @@ import javax.persistence.Version;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.rest.api.Constants.UUID_LENGTH;
|
||||||
import static org.apache.commons.lang3.StringUtils.left;
|
import static org.apache.commons.lang3.StringUtils.left;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "HFJ_BLK_IMPORT_JOB", uniqueConstraints = {
|
@Table(name = BulkImportJobEntity.HFJ_BLK_IMPORT_JOB, uniqueConstraints = {
|
||||||
@UniqueConstraint(name = "IDX_BLKIM_JOB_ID", columnNames = "JOB_ID")
|
@UniqueConstraint(name = "IDX_BLKIM_JOB_ID", columnNames = "JOB_ID")
|
||||||
})
|
})
|
||||||
public class BulkImportJobEntity implements Serializable {
|
public class BulkImportJobEntity implements Serializable {
|
||||||
|
|
||||||
|
public static final String HFJ_BLK_IMPORT_JOB = "HFJ_BLK_IMPORT_JOB";
|
||||||
|
public static final String JOB_ID = "JOB_ID";
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_BLKIMJOB_PID")
|
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_BLKIMJOB_PID")
|
||||||
@SequenceGenerator(name = "SEQ_BLKIMJOB_PID", sequenceName = "SEQ_BLKIMJOB_PID")
|
@SequenceGenerator(name = "SEQ_BLKIMJOB_PID", sequenceName = "SEQ_BLKIMJOB_PID")
|
||||||
@Column(name = "PID")
|
@Column(name = "PID")
|
||||||
private Long myId;
|
private Long myId;
|
||||||
|
|
||||||
@Column(name = "JOB_ID", length = Search.UUID_COLUMN_LENGTH, nullable = false, updatable = false)
|
@Column(name = JOB_ID, length = UUID_LENGTH, nullable = false, updatable = false)
|
||||||
private String myJobId;
|
private String myJobId;
|
||||||
@Column(name = "JOB_DESC", nullable = true, length = BulkExportJobEntity.STATUS_MESSAGE_LEN)
|
@Column(name = "JOB_DESC", nullable = true, length = BulkExportJobEntity.STATUS_MESSAGE_LEN)
|
||||||
private String myJobDescription;
|
private String myJobDescription;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.rest.api.Constants;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
|
import org.hl7.fhir.r4.model.InstantType;
|
||||||
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
|
@ -149,8 +150,8 @@ public class ResourceReindexJobEntity implements Serializable {
|
||||||
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||||
.append("id", myId)
|
.append("id", myId)
|
||||||
.append("resourceType", myResourceType)
|
.append("resourceType", myResourceType)
|
||||||
.append("thresholdLow", myThresholdLow)
|
.append("thresholdLow", new InstantType(myThresholdLow))
|
||||||
.append("thresholdHigh", myThresholdHigh);
|
.append("thresholdHigh", new InstantType(myThresholdHigh));
|
||||||
if (myDeleted) {
|
if (myDeleted) {
|
||||||
b.append("deleted", myDeleted);
|
b.append("deleted", myDeleted);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,13 +76,18 @@ import static org.apache.commons.lang3.StringUtils.left;
|
||||||
})
|
})
|
||||||
public class Search implements ICachedSearchDetails, Serializable {
|
public class Search implements ICachedSearchDetails, Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Long enough to accommodate a full UUID (36) with an additional prefix
|
||||||
|
* used by megascale (12)
|
||||||
|
*/
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public static final int UUID_COLUMN_LENGTH = 36;
|
public static final int SEARCH_UUID_COLUMN_LENGTH = 48;
|
||||||
public static final String HFJ_SEARCH = "HFJ_SEARCH";
|
public static final String HFJ_SEARCH = "HFJ_SEARCH";
|
||||||
private static final int MAX_SEARCH_QUERY_STRING = 10000;
|
private static final int MAX_SEARCH_QUERY_STRING = 10000;
|
||||||
private static final int FAILURE_MESSAGE_LENGTH = 500;
|
private static final int FAILURE_MESSAGE_LENGTH = 500;
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(Search.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(Search.class);
|
||||||
|
public static final String SEARCH_UUID = "SEARCH_UUID";
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
@Column(name = "CREATED", nullable = false, updatable = false)
|
@Column(name = "CREATED", nullable = false, updatable = false)
|
||||||
private Date myCreated;
|
private Date myCreated;
|
||||||
|
@ -136,7 +141,7 @@ public class Search implements ICachedSearchDetails, Serializable {
|
||||||
private SearchStatusEnum myStatus;
|
private SearchStatusEnum myStatus;
|
||||||
@Column(name = "TOTAL_COUNT", nullable = true)
|
@Column(name = "TOTAL_COUNT", nullable = true)
|
||||||
private Integer myTotalCount;
|
private Integer myTotalCount;
|
||||||
@Column(name = "SEARCH_UUID", length = UUID_COLUMN_LENGTH, nullable = false, updatable = false)
|
@Column(name = SEARCH_UUID, length = SEARCH_UUID_COLUMN_LENGTH, nullable = false, updatable = false)
|
||||||
private String myUuid;
|
private String myUuid;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@Version
|
@Version
|
||||||
|
@ -146,6 +151,9 @@ public class Search implements ICachedSearchDetails, Serializable {
|
||||||
@Column(name = "SEARCH_PARAM_MAP", nullable = true)
|
@Column(name = "SEARCH_PARAM_MAP", nullable = true)
|
||||||
private byte[] mySearchParameterMap;
|
private byte[] mySearchParameterMap;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private transient SearchParameterMap mySearchParameterMapTransient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This isn't currently persisted in the DB as it's only used for offset mode. We could
|
* This isn't currently persisted in the DB as it's only used for offset mode. We could
|
||||||
* change this if needed in the future.
|
* change this if needed in the future.
|
||||||
|
@ -363,10 +371,12 @@ public class Search implements ICachedSearchDetails, Serializable {
|
||||||
myTotalCount = theTotalCount;
|
myTotalCount = theTotalCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getUuid() {
|
public String getUuid() {
|
||||||
return myUuid;
|
return myUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setUuid(String theUuid) {
|
public void setUuid(String theUuid) {
|
||||||
myUuid = theUuid;
|
myUuid = theUuid;
|
||||||
}
|
}
|
||||||
|
@ -402,11 +412,26 @@ public class Search implements ICachedSearchDetails, Serializable {
|
||||||
return myVersion;
|
return myVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that this is not always set! We set this if we're storing a
|
||||||
|
* Search in {@link SearchStatusEnum#PASSCMPLET} status since we'll need
|
||||||
|
* the map in order to restart, but otherwise we save space and time by
|
||||||
|
* not storing it.
|
||||||
|
*/
|
||||||
public Optional<SearchParameterMap> getSearchParameterMap() {
|
public Optional<SearchParameterMap> getSearchParameterMap() {
|
||||||
return Optional.ofNullable(mySearchParameterMap).map(t -> SerializationUtils.deserialize(mySearchParameterMap));
|
if (mySearchParameterMapTransient != null) {
|
||||||
|
return Optional.of(mySearchParameterMapTransient);
|
||||||
|
}
|
||||||
|
SearchParameterMap searchParameterMap = null;
|
||||||
|
if (mySearchParameterMap != null) {
|
||||||
|
searchParameterMap = SerializationUtils.deserialize(mySearchParameterMap);
|
||||||
|
mySearchParameterMapTransient = searchParameterMap;
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(searchParameterMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSearchParameterMap(SearchParameterMap theSearchParameterMap) {
|
public void setSearchParameterMap(SearchParameterMap theSearchParameterMap) {
|
||||||
|
mySearchParameterMapTransient = theSearchParameterMap;
|
||||||
mySearchParameterMap = SerializationUtils.serialize(theSearchParameterMap);
|
mySearchParameterMap = SerializationUtils.serialize(theSearchParameterMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package ca.uhn.fhir.jpa.esr;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ExternallyStoredResourceAddress {
|
||||||
|
|
||||||
|
private final String myProviderId;
|
||||||
|
private final String myLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param theProviderId The ID of the provider which will handle this address. Must match the ID returned by a registered {@link IExternallyStoredResourceService}.
|
||||||
|
* @param theLocation The actual location for the provider to resolve. The format of this string is entirely up to the {@link IExternallyStoredResourceService} and only needs to make sense to it.
|
||||||
|
*/
|
||||||
|
public ExternallyStoredResourceAddress(String theProviderId, String theLocation) {
|
||||||
|
myProviderId = theProviderId;
|
||||||
|
myLocation = theLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The ID of the provider which will handle this address. Must match the ID returned by a registered {@link IExternallyStoredResourceService}.
|
||||||
|
*/
|
||||||
|
public String getProviderId() {
|
||||||
|
return myProviderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The actual location for the provider to resolve. The format of this string is entirely up to the {@link IExternallyStoredResourceService} and only needs to make sense to it.
|
||||||
|
*/
|
||||||
|
public String getLocation() {
|
||||||
|
return myLocation;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package ca.uhn.fhir.jpa.esr;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
|
|
||||||
|
public class ExternallyStoredResourceAddressMetadataKey extends ResourceMetadataKeyEnum<ExternallyStoredResourceAddress> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance
|
||||||
|
*/
|
||||||
|
public static final ExternallyStoredResourceAddressMetadataKey INSTANCE = new ExternallyStoredResourceAddressMetadataKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
private ExternallyStoredResourceAddressMetadataKey() {
|
||||||
|
super("ExternallyStoredResourceAddress", ExternallyStoredResourceAddress.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package ca.uhn.fhir.jpa.esr;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
|
|
||||||
|
public class ExternallyStoredResourceServiceRegistry {
|
||||||
|
|
||||||
|
private static final String VALID_ID_PATTERN = "[a-zA-Z0-9_.-]+";
|
||||||
|
private final Map<String, IExternallyStoredResourceService> myIdToProvider = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new provider. Do not call this method after the server has been started.
|
||||||
|
*
|
||||||
|
* @param theProvider The provider to register.
|
||||||
|
*/
|
||||||
|
public void registerProvider(@Nonnull IExternallyStoredResourceService theProvider) {
|
||||||
|
String id = defaultString(theProvider.getId());
|
||||||
|
Validate.isTrue(id.matches(VALID_ID_PATTERN), "Invalid provider ID (must match pattern " + VALID_ID_PATTERN + "): %s", id);
|
||||||
|
Validate.isTrue(!myIdToProvider.containsKey(id), "Already have a provider with ID: %s", id);
|
||||||
|
|
||||||
|
myIdToProvider.put(id, theProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do we have any providers registered?
|
||||||
|
*/
|
||||||
|
public boolean hasProviders() {
|
||||||
|
return !myIdToProvider.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all registered providers. This method is mostly intended for unit tests.
|
||||||
|
*/
|
||||||
|
public void clearProviders() {
|
||||||
|
myIdToProvider.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public IExternallyStoredResourceService getProvider(@Nonnull String theProviderId) {
|
||||||
|
IExternallyStoredResourceService retVal = myIdToProvider.get(theProviderId);
|
||||||
|
Validate.notNull(retVal, "Invalid ESR provider ID: %s", theProviderId);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package ca.uhn.fhir.jpa.esr;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
public interface IExternallyStoredResourceService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of this provider. No two providers may return the same
|
||||||
|
* ID, and this provider should always return the same ID.
|
||||||
|
*/
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the given resource using the given address string
|
||||||
|
*
|
||||||
|
* @param theAddress The address string is a format that is entirely up to the individual provider. HAPI FHIR
|
||||||
|
* doesn't try to understand it.
|
||||||
|
* @return HAPI FHIR may modify the returned object, so it is important to always return a new object for every call here (careful with caching!)
|
||||||
|
*/
|
||||||
|
IBaseResource fetchResource(String theAddress);
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This package stores the mechanism for Externally Stored Resources (ESRs).
|
||||||
|
* <p>
|
||||||
|
* A normal resource stores its contents in the {@link ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable}
|
||||||
|
* entity as text (compressed JSON or otherwise) with one row in that table per version of the
|
||||||
|
* resource. An ESR still creates a row in that table, but stores a reference to actual body
|
||||||
|
* storage instead.
|
||||||
|
* <p>
|
||||||
|
* THIS IS AN EXPERIMENTAL API - IT MAY GO AWAY OR CHANGE IN THE FUTURE
|
||||||
|
*
|
||||||
|
* @since 6.6.0
|
||||||
|
*/
|
||||||
|
package ca.uhn.fhir.jpa.esr;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
|
@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.migrate.tasks;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.jpa.entity.BulkExportJobEntity;
|
||||||
|
import ca.uhn.fhir.jpa.entity.BulkImportJobEntity;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
|
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
|
import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
|
||||||
|
@ -31,13 +33,13 @@ import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
|
||||||
import ca.uhn.fhir.jpa.migrate.tasks.api.Builder;
|
import ca.uhn.fhir.jpa.migrate.tasks.api.Builder;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
||||||
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
|
||||||
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
|
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
||||||
import ca.uhn.fhir.util.VersionEnum;
|
import ca.uhn.fhir.util.VersionEnum;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -48,6 +50,8 @@ import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.rest.api.Constants.UUID_LENGTH;
|
||||||
|
|
||||||
@SuppressWarnings({"SqlNoDataSourceInspection", "SpellCheckingInspection"})
|
@SuppressWarnings({"SqlNoDataSourceInspection", "SpellCheckingInspection"})
|
||||||
public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
|
|
||||||
|
@ -86,15 +90,13 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
init600(); // 20211102 -
|
init600(); // 20211102 -
|
||||||
init610();
|
init610();
|
||||||
init620();
|
init620();
|
||||||
init630();
|
|
||||||
init640();
|
init640();
|
||||||
init660();
|
init660();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init660() {
|
protected void init660() {
|
||||||
|
|
||||||
|
|
||||||
Builder version = forVersion(VersionEnum.V6_6_0);
|
Builder version = forVersion(VersionEnum.V6_6_0);
|
||||||
|
|
||||||
// fix Postgres clob types - that stupid oid driver problem is still there
|
// fix Postgres clob types - that stupid oid driver problem is still there
|
||||||
// BT2_JOB_INSTANCE.PARAMS_JSON_LOB
|
// BT2_JOB_INSTANCE.PARAMS_JSON_LOB
|
||||||
version.onTable("BT2_JOB_INSTANCE")
|
version.onTable("BT2_JOB_INSTANCE")
|
||||||
|
@ -105,12 +107,25 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
// BT2_WORK_CHUNK.CHUNK_DATA
|
// BT2_WORK_CHUNK.CHUNK_DATA
|
||||||
version.onTable("BT2_WORK_CHUNK")
|
version.onTable("BT2_WORK_CHUNK")
|
||||||
.migratePostgresTextClobToBinaryClob("20230208.3", "CHUNK_DATA");
|
.migratePostgresTextClobToBinaryClob("20230208.3", "CHUNK_DATA");
|
||||||
|
|
||||||
|
version
|
||||||
|
.onTable(Search.HFJ_SEARCH)
|
||||||
|
.addColumn("20230215.1", Search.SEARCH_UUID)
|
||||||
|
.nullable()
|
||||||
|
.type(ColumnTypeEnum.STRING, Search.SEARCH_UUID_COLUMN_LENGTH);
|
||||||
|
version
|
||||||
|
.onTable(BulkImportJobEntity.HFJ_BLK_IMPORT_JOB)
|
||||||
|
.addColumn("20230215.2", BulkImportJobEntity.JOB_ID)
|
||||||
|
.nullable()
|
||||||
|
.type(ColumnTypeEnum.STRING, UUID_LENGTH);
|
||||||
|
version
|
||||||
|
.onTable(BulkExportJobEntity.HFJ_BLK_EXPORT_JOB)
|
||||||
|
.addColumn("20230215.3", BulkExportJobEntity.JOB_ID)
|
||||||
|
.nullable()
|
||||||
|
.type(ColumnTypeEnum.STRING, UUID_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init640() {
|
protected void init640() {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void init630() {
|
|
||||||
Builder version = forVersion(VersionEnum.V6_3_0);
|
Builder version = forVersion(VersionEnum.V6_3_0);
|
||||||
|
|
||||||
// start forced_id inline migration
|
// start forced_id inline migration
|
||||||
|
@ -822,7 +837,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
// Bulk Import Job
|
// Bulk Import Job
|
||||||
Builder.BuilderAddTableByColumns blkImportJobTable = version.addTableByColumns("20210410.1", "HFJ_BLK_IMPORT_JOB", "PID");
|
Builder.BuilderAddTableByColumns blkImportJobTable = version.addTableByColumns("20210410.1", "HFJ_BLK_IMPORT_JOB", "PID");
|
||||||
blkImportJobTable.addColumn("PID").nonNullable().type(ColumnTypeEnum.LONG);
|
blkImportJobTable.addColumn("PID").nonNullable().type(ColumnTypeEnum.LONG);
|
||||||
blkImportJobTable.addColumn("JOB_ID").nonNullable().type(ColumnTypeEnum.STRING, Search.UUID_COLUMN_LENGTH);
|
blkImportJobTable.addColumn("JOB_ID").nonNullable().type(ColumnTypeEnum.STRING, UUID_LENGTH);
|
||||||
blkImportJobTable.addColumn("JOB_STATUS").nonNullable().type(ColumnTypeEnum.STRING, 10);
|
blkImportJobTable.addColumn("JOB_STATUS").nonNullable().type(ColumnTypeEnum.STRING, 10);
|
||||||
blkImportJobTable.addColumn("STATUS_TIME").nonNullable().type(ColumnTypeEnum.DATE_TIMESTAMP);
|
blkImportJobTable.addColumn("STATUS_TIME").nonNullable().type(ColumnTypeEnum.DATE_TIMESTAMP);
|
||||||
blkImportJobTable.addColumn("STATUS_MESSAGE").nullable().type(ColumnTypeEnum.STRING, 500);
|
blkImportJobTable.addColumn("STATUS_MESSAGE").nullable().type(ColumnTypeEnum.STRING, 500);
|
||||||
|
@ -1500,7 +1515,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
version.onTable("HFJ_SEARCH")
|
version.onTable("HFJ_SEARCH")
|
||||||
.addColumn("20190814.6", "SEARCH_PARAM_MAP").nullable().type(ColumnTypeEnum.BLOB);
|
.addColumn("20190814.6", "SEARCH_PARAM_MAP").nullable().type(ColumnTypeEnum.BLOB);
|
||||||
version.onTable("HFJ_SEARCH")
|
version.onTable("HFJ_SEARCH")
|
||||||
.modifyColumn("20190814.7", "SEARCH_UUID").nonNullable().withType(ColumnTypeEnum.STRING, 36);
|
.modifyColumn("20190814.7", "SEARCH_UUID").nonNullable().withType(ColumnTypeEnum.STRING, Search.SEARCH_UUID_COLUMN_LENGTH);
|
||||||
|
|
||||||
version.onTable("HFJ_SEARCH_PARM").dropThisTable("20190814.8");
|
version.onTable("HFJ_SEARCH_PARM").dropThisTable("20190814.8");
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.BasePagingProvider;
|
import ca.uhn.fhir.rest.server.BasePagingProvider;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
// Note: this class is not annotated with @Service because we want to
|
// Note: this class is not annotated with @Service because we want to
|
||||||
// explicitly define it in BaseConfig.java. This is done so that
|
// explicitly define it in BaseConfig.java. This is done so that
|
||||||
// implementors can override if they want to.
|
// implementors can override if they want to.
|
||||||
|
@ -51,6 +53,7 @@ public class DatabaseBackedPagingProvider extends BasePagingProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
*
|
||||||
* @deprecated Use {@link DatabaseBackedPagingProvider} as this constructor has no purpose
|
* @deprecated Use {@link DatabaseBackedPagingProvider} as this constructor has no purpose
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@ -60,12 +63,19 @@ public class DatabaseBackedPagingProvider extends BasePagingProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized IBundleProvider retrieveResultList(RequestDetails theRequestDetails, String theId) {
|
public synchronized IBundleProvider retrieveResultList(RequestDetails theRequestDetails, String theId) {
|
||||||
myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, "Bundle", null, null);
|
|
||||||
PersistedJpaBundleProvider provider = myPersistedJpaBundleProviderFactory.newInstance(theRequestDetails, theId);
|
PersistedJpaBundleProvider provider = myPersistedJpaBundleProviderFactory.newInstance(theRequestDetails, theId);
|
||||||
if (!provider.ensureSearchEntityLoaded()) {
|
return validateAndReturnBundleProvider(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses may override in order to modify the bundle provider being returned
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
protected PersistedJpaBundleProvider validateAndReturnBundleProvider(PersistedJpaBundleProvider theBundleProvider) {
|
||||||
|
if (!theBundleProvider.ensureSearchEntityLoaded()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return provider;
|
return theBundleProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
|
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
|
@ -40,9 +41,10 @@ import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||||
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||||
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
|
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||||
import ca.uhn.fhir.jpa.util.QueryParameterUtils;
|
import ca.uhn.fhir.jpa.util.QueryParameterUtils;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
|
@ -98,7 +100,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISearchCacheSvc mySearchCacheSvc;
|
private ISearchCacheSvc mySearchCacheSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private RequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private JpaStorageSettings myStorageSettings;
|
private JpaStorageSettings myStorageSettings;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -130,6 +132,11 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
myUuid = theSearch.getUuid();
|
myUuid = theSearch.getUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setRequestPartitionHelperSvcForUnitTest(IRequestPartitionHelperSvc theRequestPartitionHelperSvc) {
|
||||||
|
myRequestPartitionHelperSvc = theRequestPartitionHelperSvc;
|
||||||
|
}
|
||||||
|
|
||||||
protected Search getSearchEntity() {
|
protected Search getSearchEntity() {
|
||||||
return mySearchEntity;
|
return mySearchEntity;
|
||||||
}
|
}
|
||||||
|
@ -148,7 +155,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder(mySearchEntity.getResourceType(),
|
HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder(mySearchEntity.getResourceType(),
|
||||||
mySearchEntity.getResourceId(), mySearchEntity.getLastUpdatedLow(), mySearchEntity.getLastUpdatedHigh());
|
mySearchEntity.getResourceId(), mySearchEntity.getLastUpdatedLow(), mySearchEntity.getLastUpdatedHigh());
|
||||||
|
|
||||||
RequestPartitionId partitionId = getRequestPartitionIdForHistory();
|
RequestPartitionId partitionId = getRequestPartitionId();
|
||||||
List<ResourceHistoryTable> results = historyBuilder.fetchEntities(partitionId, theOffset, theFromIndex,
|
List<ResourceHistoryTable> results = historyBuilder.fetchEntities(partitionId, theOffset, theFromIndex,
|
||||||
theToIndex, mySearchEntity.getHistorySearchStyle());
|
theToIndex, mySearchEntity.getHistorySearchStyle());
|
||||||
|
|
||||||
|
@ -194,18 +201,26 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private RequestPartitionId getRequestPartitionIdForHistory() {
|
protected final RequestPartitionId getRequestPartitionId() {
|
||||||
if (myRequestPartitionId == null) {
|
if (myRequestPartitionId == null) {
|
||||||
if (mySearchEntity.getResourceId() != null) {
|
ReadPartitionIdRequestDetails details;
|
||||||
// If we have an ID, we've already checked the partition and made sure it's appropriate
|
if (mySearchEntity == null) {
|
||||||
myRequestPartitionId = RequestPartitionId.allPartitions();
|
details = ReadPartitionIdRequestDetails.forSearchUuid(myUuid);
|
||||||
|
} else if (mySearchEntity.getSearchType() == SearchTypeEnum.HISTORY) {
|
||||||
|
details = ReadPartitionIdRequestDetails.forHistory(mySearchEntity.getResourceType(), null);
|
||||||
} else {
|
} else {
|
||||||
myRequestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(myRequest, mySearchEntity.getResourceType(), null);
|
SearchParameterMap params = mySearchEntity.getSearchParameterMap().orElse(null);
|
||||||
|
details = ReadPartitionIdRequestDetails.forSearchType(mySearchEntity.getResourceType(), params, null);
|
||||||
}
|
}
|
||||||
|
myRequestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(myRequest, details);
|
||||||
}
|
}
|
||||||
return myRequestPartitionId;
|
return myRequestPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRequestPartitionId(RequestPartitionId theRequestPartitionId) {
|
||||||
|
myRequestPartitionId = theRequestPartitionId;
|
||||||
|
}
|
||||||
|
|
||||||
protected List<IBaseResource> doSearchOrEverything(final int theFromIndex, final int theToIndex) {
|
protected List<IBaseResource> doSearchOrEverything(final int theFromIndex, final int theToIndex) {
|
||||||
if (mySearchEntity.getTotalCount() != null && mySearchEntity.getNumFound() <= 0) {
|
if (mySearchEntity.getTotalCount() != null && mySearchEntity.getNumFound() <= 0) {
|
||||||
// No resources to fetch (e.g. we did a _summary=count search)
|
// No resources to fetch (e.g. we did a _summary=count search)
|
||||||
|
@ -217,8 +232,14 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
|
|
||||||
final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(dao, resourceName, resourceType);
|
final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(dao, resourceName, resourceType);
|
||||||
|
|
||||||
final List<JpaPid> pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex, myRequest);
|
RequestPartitionId requestPartitionId = getRequestPartitionId();
|
||||||
return myTxService.withRequest(myRequest).execute(() -> toResourceList(sb, pidsSubList));
|
final List<JpaPid> pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex, myRequest, requestPartitionId);
|
||||||
|
return myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
|
return toResourceList(sb, pidsSubList);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -226,7 +247,10 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
*/
|
*/
|
||||||
public boolean ensureSearchEntityLoaded() {
|
public boolean ensureSearchEntityLoaded() {
|
||||||
if (mySearchEntity == null) {
|
if (mySearchEntity == null) {
|
||||||
Optional<Search> searchOpt = myTxService.withRequest(myRequest).execute(() -> mySearchCacheSvc.fetchByUuid(myUuid));
|
Optional<Search> searchOpt = myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(myRequestPartitionId)
|
||||||
|
.execute(() -> mySearchCacheSvc.fetchByUuid(myUuid));
|
||||||
if (!searchOpt.isPresent()) {
|
if (!searchOpt.isPresent()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -263,9 +287,12 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
key = MemoryCacheService.HistoryCountKey.forSystem();
|
key = MemoryCacheService.HistoryCountKey.forSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
Function<MemoryCacheService.HistoryCountKey, Integer> supplier = k -> myTxService.withRequest(myRequest).execute(() -> {
|
Function<MemoryCacheService.HistoryCountKey, Integer> supplier = k -> myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(getRequestPartitionId())
|
||||||
|
.execute(() -> {
|
||||||
HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder(mySearchEntity.getResourceType(), mySearchEntity.getResourceId(), mySearchEntity.getLastUpdatedLow(), mySearchEntity.getLastUpdatedHigh());
|
HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder(mySearchEntity.getResourceType(), mySearchEntity.getResourceId(), mySearchEntity.getLastUpdatedLow(), mySearchEntity.getLastUpdatedHigh());
|
||||||
Long count = historyBuilder.fetchCount(getRequestPartitionIdForHistory());
|
Long count = historyBuilder.fetchCount(getRequestPartitionId());
|
||||||
return count.intValue();
|
return count.intValue();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -307,7 +334,10 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
|
|
||||||
switch (mySearchEntity.getSearchType()) {
|
switch (mySearchEntity.getSearchType()) {
|
||||||
case HISTORY:
|
case HISTORY:
|
||||||
return myTxService.withRequest(myRequest).execute(() -> doHistoryInTransaction(mySearchEntity.getOffset(), theFromIndex, theToIndex));
|
return myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(getRequestPartitionId())
|
||||||
|
.execute(() -> doHistoryInTransaction(mySearchEntity.getOffset(), theFromIndex, theToIndex));
|
||||||
case SEARCH:
|
case SEARCH:
|
||||||
case EVERYTHING:
|
case EVERYTHING:
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.search;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.config.JpaConfig;
|
import ca.uhn.fhir.jpa.config.JpaConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
|
@ -52,16 +53,16 @@ public class PersistedJpaBundleProviderFactory {
|
||||||
return (PersistedJpaBundleProvider) retVal;
|
return (PersistedJpaBundleProvider) retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PersistedJpaSearchFirstPageBundleProvider newInstanceFirstPage(RequestDetails theRequestDetails, Search theSearch, SearchTask theTask, ISearchBuilder theSearchBuilder) {
|
public PersistedJpaSearchFirstPageBundleProvider newInstanceFirstPage(RequestDetails theRequestDetails, Search theSearch, SearchTask theTask, ISearchBuilder theSearchBuilder, RequestPartitionId theRequestPartitionId) {
|
||||||
return (PersistedJpaSearchFirstPageBundleProvider) myApplicationContext.getBean(JpaConfig.PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER, theRequestDetails, theSearch, theTask, theSearchBuilder);
|
return (PersistedJpaSearchFirstPageBundleProvider) myApplicationContext.getBean(JpaConfig.PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER, theRequestDetails, theSearch, theTask, theSearchBuilder, theRequestPartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset) {
|
public IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset, RequestPartitionId theRequestPartitionId) {
|
||||||
return history(theRequest, theResourceType, theResourcePid, theRangeStartInclusive, theRangeEndInclusive, theOffset, null);
|
return history(theRequest, theResourceType, theResourcePid, theRangeStartInclusive, theRangeEndInclusive, theOffset, null, theRequestPartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset, HistorySearchStyleEnum searchParameterType) {
|
public IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset, HistorySearchStyleEnum searchParameterType, RequestPartitionId theRequestPartitionId) {
|
||||||
String resourceName = defaultIfBlank(theResourceType, null);
|
String resourceName = defaultIfBlank(theResourceType, null);
|
||||||
|
|
||||||
Search search = new Search();
|
Search search = new Search();
|
||||||
|
@ -76,7 +77,10 @@ public class PersistedJpaBundleProviderFactory {
|
||||||
search.setStatus(SearchStatusEnum.FINISHED);
|
search.setStatus(SearchStatusEnum.FINISHED);
|
||||||
search.setHistorySearchStyle(searchParameterType);
|
search.setHistorySearchStyle(searchParameterType);
|
||||||
|
|
||||||
return newInstance(theRequest, search);
|
PersistedJpaBundleProvider provider = newInstance(theRequest, search);
|
||||||
|
provider.setRequestPartitionId(theRequestPartitionId);
|
||||||
|
|
||||||
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,17 +20,17 @@ package ca.uhn.fhir.jpa.search;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
||||||
import ca.uhn.fhir.jpa.search.builder.tasks.SearchTask;
|
import ca.uhn.fhir.jpa.search.builder.tasks.SearchTask;
|
||||||
import ca.uhn.fhir.jpa.util.QueryParameterUtils;
|
import ca.uhn.fhir.jpa.util.QueryParameterUtils;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -48,11 +48,15 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public PersistedJpaSearchFirstPageBundleProvider(Search theSearch, SearchTask theSearchTask, ISearchBuilder theSearchBuilder, RequestDetails theRequest) {
|
public PersistedJpaSearchFirstPageBundleProvider(Search theSearch, SearchTask theSearchTask, ISearchBuilder theSearchBuilder, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||||
super(theRequest, theSearch.getUuid());
|
super(theRequest, theSearch.getUuid());
|
||||||
|
|
||||||
|
assert theSearch.getSearchType() != SearchTypeEnum.HISTORY;
|
||||||
|
|
||||||
setSearchEntity(theSearch);
|
setSearchEntity(theSearch);
|
||||||
mySearchTask = theSearchTask;
|
mySearchTask = theSearchTask;
|
||||||
mySearchBuilder = theSearchBuilder;
|
mySearchBuilder = theSearchBuilder;
|
||||||
|
super.setRequestPartitionId(theRequestPartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -67,9 +71,12 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
|
||||||
final List<JpaPid> pids = mySearchTask.getResourcePids(theFromIndex, theToIndex);
|
final List<JpaPid> pids = mySearchTask.getResourcePids(theFromIndex, theToIndex);
|
||||||
ourLog.trace("Done fetching search resource PIDs");
|
ourLog.trace("Done fetching search resource PIDs");
|
||||||
|
|
||||||
List<IBaseResource> retVal = myTxService.withRequest(myRequest).execute(() -> {
|
RequestPartitionId requestPartitionId = getRequestPartitionId();
|
||||||
return toResourceList(mySearchBuilder, pids);
|
|
||||||
});
|
List<IBaseResource> retVal = myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> toResourceList(mySearchBuilder, pids));
|
||||||
|
|
||||||
long totalCountWanted = theToIndex - theFromIndex;
|
long totalCountWanted = theToIndex - theFromIndex;
|
||||||
long totalCountMatch = (int) retVal
|
long totalCountMatch = (int) retVal
|
||||||
|
|
|
@ -69,7 +69,7 @@ import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.data.domain.AbstractPageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -108,7 +108,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
private final SearchBuilderFactory<JpaPid> mySearchBuilderFactory;
|
private final SearchBuilderFactory<JpaPid> mySearchBuilderFactory;
|
||||||
private final ISynchronousSearchSvc mySynchronousSearchSvc;
|
private final ISynchronousSearchSvc mySynchronousSearchSvc;
|
||||||
private final PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
private final PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
||||||
private final IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
|
||||||
private final ISearchParamRegistry mySearchParamRegistry;
|
private final ISearchParamRegistry mySearchParamRegistry;
|
||||||
private final SearchStrategyFactory mySearchStrategyFactory;
|
private final SearchStrategyFactory mySearchStrategyFactory;
|
||||||
private final ExceptionService myExceptionSvc;
|
private final ExceptionService myExceptionSvc;
|
||||||
|
@ -154,7 +153,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
mySearchBuilderFactory = theSearchBuilderFactory;
|
mySearchBuilderFactory = theSearchBuilderFactory;
|
||||||
mySynchronousSearchSvc = theSynchronousSearchSvc;
|
mySynchronousSearchSvc = theSynchronousSearchSvc;
|
||||||
myPersistedJpaBundleProviderFactory = thePersistedJpaBundleProviderFactory;
|
myPersistedJpaBundleProviderFactory = thePersistedJpaBundleProviderFactory;
|
||||||
myRequestPartitionHelperService = theRequestPartitionHelperService;
|
|
||||||
mySearchParamRegistry = theSearchParamRegistry;
|
mySearchParamRegistry = theSearchParamRegistry;
|
||||||
mySearchStrategyFactory = theSearchStrategyFactory;
|
mySearchStrategyFactory = theSearchStrategyFactory;
|
||||||
myExceptionSvc = theExceptionSvc;
|
myExceptionSvc = theExceptionSvc;
|
||||||
|
@ -201,9 +199,19 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
/**
|
/**
|
||||||
* This method is called by the HTTP client processing thread in order to
|
* This method is called by the HTTP client processing thread in order to
|
||||||
* fetch resources.
|
* fetch resources.
|
||||||
|
* <p>
|
||||||
|
* This method must not be called from inside a transaction. The rationale is that
|
||||||
|
* the {@link Search} entity is treated as a piece of shared state across client threads
|
||||||
|
* accessing the same thread, so we need to be able to update that table in a transaction
|
||||||
|
* and commit it right away in order for that to work. Examples of needing to do this
|
||||||
|
* include if two different clients request the same search and are both paging at the
|
||||||
|
* same time, but also includes clients that are hacking the paging links to
|
||||||
|
* fetch multiple pages of a search result in parallel. In both cases we need to only
|
||||||
|
* let one of them actually activate the search, or we will have conficts. The other thread
|
||||||
|
* just needs to wait until the first one actually fetches more results.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<JpaPid> getResources(final String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails) {
|
public List<JpaPid> getResources(final String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
|
||||||
assert !TransactionSynchronizationManager.isActualTransactionActive();
|
assert !TransactionSynchronizationManager.isActualTransactionActive();
|
||||||
|
|
||||||
// If we're actively searching right now, don't try to do anything until at least one batch has been
|
// If we're actively searching right now, don't try to do anything until at least one batch has been
|
||||||
|
@ -240,13 +248,12 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
Callable<Search> searchCallback = () -> mySearchCacheSvc
|
Callable<Search> searchCallback = () -> mySearchCacheSvc
|
||||||
.fetchByUuid(theUuid)
|
.fetchByUuid(theUuid)
|
||||||
.orElseThrow(() -> myExceptionSvc.newUnknownSearchException(theUuid));
|
.orElseThrow(() -> myExceptionSvc.newUnknownSearchException(theUuid));
|
||||||
if (theRequestDetails != null) {
|
search = myTxService
|
||||||
search = myTxService.withRequest(theRequestDetails).execute(searchCallback);
|
.withRequest(theRequestDetails)
|
||||||
} else {
|
.withRequestPartitionId(theRequestPartitionId)
|
||||||
search = HapiTransactionService.invokeCallableAndHandleAnyException(searchCallback);
|
.execute(searchCallback);
|
||||||
}
|
|
||||||
|
|
||||||
QueryParameterUtils.verifySearchHasntFailedOrThrowInternalErrorException(search);
|
QueryParameterUtils.verifySearchHasntFailedOrThrowInternalErrorException(search);
|
||||||
|
|
||||||
if (search.getStatus() == SearchStatusEnum.FINISHED) {
|
if (search.getStatus() == SearchStatusEnum.FINISHED) {
|
||||||
ourLog.trace("Search entity marked as finished with {} results", search.getNumFound());
|
ourLog.trace("Search entity marked as finished with {} results", search.getNumFound());
|
||||||
break;
|
break;
|
||||||
|
@ -272,7 +279,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
String resourceType = search.getResourceType();
|
String resourceType = search.getResourceType();
|
||||||
SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search"));
|
SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search"));
|
||||||
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(resourceType);
|
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(resourceType);
|
||||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, resourceType, params, null);
|
|
||||||
|
|
||||||
SearchTaskParameters parameters = new SearchTaskParameters(
|
SearchTaskParameters parameters = new SearchTaskParameters(
|
||||||
search,
|
search,
|
||||||
|
@ -280,7 +286,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
params,
|
params,
|
||||||
resourceType,
|
resourceType,
|
||||||
theRequestDetails,
|
theRequestDetails,
|
||||||
requestPartitionId,
|
theRequestPartitionId,
|
||||||
myOnRemoveSearchTask,
|
myOnRemoveSearchTask,
|
||||||
mySyncSize
|
mySyncSize
|
||||||
);
|
);
|
||||||
|
@ -299,7 +305,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
|
|
||||||
ourLog.trace("Finished looping");
|
ourLog.trace("Finished looping");
|
||||||
|
|
||||||
List<JpaPid> pids = fetchResultPids(theUuid, theFrom, theTo, theRequestDetails, search);
|
List<JpaPid> pids = fetchResultPids(theUuid, theFrom, theTo, theRequestDetails, search, theRequestPartitionId);
|
||||||
|
|
||||||
ourLog.trace("Fetched {} results", pids.size());
|
ourLog.trace("Fetched {} results", pids.size());
|
||||||
|
|
||||||
|
@ -307,8 +313,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private List<JpaPid> fetchResultPids(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails, Search theSearch) {
|
private List<JpaPid> fetchResultPids(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails, Search theSearch, RequestPartitionId theRequestPartitionId) {
|
||||||
List<JpaPid> pids = myTxService.withRequest(theRequestDetails).execute(() -> mySearchResultCacheSvc.fetchResultPids(theSearch, theFrom, theTo));
|
List<JpaPid> pids = mySearchResultCacheSvc.fetchResultPids(theSearch, theFrom, theTo, theRequestDetails, theRequestPartitionId);
|
||||||
if (pids == null) {
|
if (pids == null) {
|
||||||
throw myExceptionSvc.newUnknownSearchException(theUuid);
|
throw myExceptionSvc.newUnknownSearchException(theUuid);
|
||||||
}
|
}
|
||||||
|
@ -325,7 +331,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
Search search = new Search();
|
Search search = new Search();
|
||||||
QueryParameterUtils.populateSearchEntity(theParams, theResourceType, searchUuid, queryString, search, theRequestPartitionId);
|
QueryParameterUtils.populateSearchEntity(theParams, theResourceType, searchUuid, queryString, search, theRequestPartitionId);
|
||||||
|
|
||||||
myStorageInterceptorHooks.callStoragePresearchRegistered(theRequestDetails, theParams, search);
|
myStorageInterceptorHooks.callStoragePresearchRegistered(theRequestDetails, theParams, search, theRequestPartitionId);
|
||||||
|
|
||||||
validateSearch(theParams);
|
validateSearch(theParams);
|
||||||
|
|
||||||
|
@ -483,7 +489,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
myIdToSearchTask.put(theSearch.getUuid(), task);
|
myIdToSearchTask.put(theSearch.getUuid(), task);
|
||||||
task.call();
|
task.call();
|
||||||
|
|
||||||
PersistedJpaSearchFirstPageBundleProvider retVal = myPersistedJpaBundleProviderFactory.newInstanceFirstPage(theRequestDetails, theSearch, task, theSb);
|
PersistedJpaSearchFirstPageBundleProvider retVal = myPersistedJpaBundleProviderFactory.newInstanceFirstPage(theRequestDetails, theSearch, task, theSb, theRequestPartitionId);
|
||||||
|
|
||||||
ourLog.debug("Search initial phase completed in {}ms", w.getMillis());
|
ourLog.debug("Search initial phase completed in {}ms", w.getMillis());
|
||||||
return retVal;
|
return retVal;
|
||||||
|
@ -492,7 +498,10 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
@Nullable
|
@Nullable
|
||||||
private PersistedJpaBundleProvider findCachedQuery(SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString, RequestPartitionId theRequestPartitionId) {
|
private PersistedJpaBundleProvider findCachedQuery(SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString, RequestPartitionId theRequestPartitionId) {
|
||||||
// May be null
|
// May be null
|
||||||
return myTxService.withRequest(theRequestDetails).execute(() -> {
|
return myTxService
|
||||||
|
.withRequest(theRequestDetails)
|
||||||
|
.withRequestPartitionId(theRequestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
|
|
||||||
// Interceptor call: STORAGE_PRECHECK_FOR_CACHED_SEARCH
|
// Interceptor call: STORAGE_PRECHECK_FOR_CACHED_SEARCH
|
||||||
HookParams params = new HookParams()
|
HookParams params = new HookParams()
|
||||||
|
@ -563,7 +572,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
|
|
||||||
int pageIndex = theFromIndex / pageSize;
|
int pageIndex = theFromIndex / pageSize;
|
||||||
|
|
||||||
Pageable page = new AbstractPageRequest(pageIndex, pageSize) {
|
return new PageRequest(pageIndex, pageSize, Sort.unsorted()) {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -571,33 +580,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
return theFromIndex;
|
return theFromIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Sort getSort() {
|
|
||||||
return Sort.unsorted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pageable next() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pageable previous() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pageable first() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pageable withPage(int theI) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return page;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
|
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
@ -85,6 +86,9 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc {
|
||||||
@Autowired
|
@Autowired
|
||||||
private EntityManager myEntityManager;
|
private EntityManager myEntityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||||
|
|
||||||
private int mySyncSize = 250;
|
private int mySyncSize = 250;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -97,8 +101,11 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc {
|
||||||
boolean wantCount = theParamWantOnlyCount || theParamOrConfigWantCount;
|
boolean wantCount = theParamWantOnlyCount || theParamOrConfigWantCount;
|
||||||
|
|
||||||
// Execute the query and make sure we return distinct results
|
// Execute the query and make sure we return distinct results
|
||||||
|
return myTxService
|
||||||
return myTxService.withRequest(theRequestDetails).readOnly().execute(() -> {
|
.withRequest(theRequestDetails)
|
||||||
|
.withRequestPartitionId(theRequestPartitionId)
|
||||||
|
.readOnly()
|
||||||
|
.execute(() -> {
|
||||||
|
|
||||||
// Load the results synchronously
|
// Load the results synchronously
|
||||||
final List<JpaPid> pids = new ArrayList<>();
|
final List<JpaPid> pids = new ArrayList<>();
|
||||||
|
|
|
@ -878,7 +878,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
resourceId.setVersion(version);
|
resourceId.setVersion(version);
|
||||||
if (version != null && !version.equals(next.getVersion())) {
|
if (version != null && !version.equals(next.getVersion())) {
|
||||||
IFhirResourceDao<? extends IBaseResource> dao = myDaoRegistry.getResourceDao(resourceType);
|
IFhirResourceDao<? extends IBaseResource> dao = myDaoRegistry.getResourceDao(resourceType);
|
||||||
next = dao.readEntity(next.getIdDt().withVersion(Long.toString(version)), null);
|
next = (IBaseResourceEntity) dao.readEntity(next.getIdDt().withVersion(Long.toString(version)), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,8 @@ package ca.uhn.fhir.jpa.search.builder;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
@ -46,13 +46,15 @@ public class StorageInterceptorHooksFacade {
|
||||||
* @param theRequestDetails
|
* @param theRequestDetails
|
||||||
* @param theParams
|
* @param theParams
|
||||||
* @param search
|
* @param search
|
||||||
|
* @param theRequestPartitionId
|
||||||
*/
|
*/
|
||||||
public void callStoragePresearchRegistered(RequestDetails theRequestDetails, SearchParameterMap theParams, Search search) {
|
public void callStoragePresearchRegistered(RequestDetails theRequestDetails, SearchParameterMap theParams, Search search, RequestPartitionId theRequestPartitionId) {
|
||||||
HookParams params = new HookParams()
|
HookParams params = new HookParams()
|
||||||
.add(ICachedSearchDetails.class, search)
|
.add(ICachedSearchDetails.class, search)
|
||||||
.add(RequestDetails.class, theRequestDetails)
|
.add(RequestDetails.class, theRequestDetails)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||||
.add(SearchParameterMap.class, theParams);
|
.add(SearchParameterMap.class, theParams)
|
||||||
|
.add(RequestPartitionId.class, theRequestPartitionId);
|
||||||
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
|
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
|
||||||
}
|
}
|
||||||
//private IInterceptorBroadcaster myInterceptorBroadcaster;
|
//private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.search.builder.tasks;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
|
@ -74,8 +75,12 @@ public class SearchContinuationTask extends SearchTask {
|
||||||
@Override
|
@Override
|
||||||
public Void call() {
|
public Void call() {
|
||||||
try {
|
try {
|
||||||
myTxService.withRequest(myRequestDetails).execute(() -> {
|
RequestPartitionId requestPartitionId = getRequestPartitionId();
|
||||||
List<JpaPid> previouslyAddedResourcePids = mySearchResultCacheSvc.fetchAllResultPids(getSearch());
|
myTxService
|
||||||
|
.withRequest(myRequestDetails)
|
||||||
|
.withRequestPartitionId(requestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
|
List<JpaPid> previouslyAddedResourcePids = mySearchResultCacheSvc.fetchAllResultPids(getSearch(), myRequestDetails, requestPartitionId);
|
||||||
if (previouslyAddedResourcePids == null) {
|
if (previouslyAddedResourcePids == null) {
|
||||||
throw myExceptionSvc.newUnknownSearchException(getSearch().getUuid());
|
throw myExceptionSvc.newUnknownSearchException(getSearch().getUuid());
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,10 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
public class SearchTask implements Callable<Void> {
|
public class SearchTask implements Callable<Void> {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchTask.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchTask.class);
|
||||||
|
// injected beans
|
||||||
|
protected final HapiTransactionService myTxService;
|
||||||
|
protected final FhirContext myContext;
|
||||||
|
protected final ISearchResultCacheSvc mySearchResultCacheSvc;
|
||||||
private final SearchParameterMap myParams;
|
private final SearchParameterMap myParams;
|
||||||
private final IDao myCallingDao;
|
private final IDao myCallingDao;
|
||||||
private final String myResourceType;
|
private final String myResourceType;
|
||||||
|
@ -104,6 +107,14 @@ public class SearchTask implements Callable<Void> {
|
||||||
private final RequestPartitionId myRequestPartitionId;
|
private final RequestPartitionId myRequestPartitionId;
|
||||||
private final SearchRuntimeDetails mySearchRuntimeDetails;
|
private final SearchRuntimeDetails mySearchRuntimeDetails;
|
||||||
private final Transaction myParentTransaction;
|
private final Transaction myParentTransaction;
|
||||||
|
private final Consumer<String> myOnRemove;
|
||||||
|
private final int mySyncSize;
|
||||||
|
private final Integer myLoadingThrottleForUnitTests;
|
||||||
|
private final IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
private final SearchBuilderFactory<JpaPid> mySearchBuilderFactory;
|
||||||
|
private final JpaStorageSettings myStorageSettings;
|
||||||
|
private final ISearchCacheSvc mySearchCacheSvc;
|
||||||
|
private final IPagingProvider myPagingProvider;
|
||||||
private Search mySearch;
|
private Search mySearch;
|
||||||
private boolean myAbortRequested;
|
private boolean myAbortRequested;
|
||||||
private int myCountSavedTotal = 0;
|
private int myCountSavedTotal = 0;
|
||||||
|
@ -112,22 +123,6 @@ public class SearchTask implements Callable<Void> {
|
||||||
private boolean myAdditionalPrefetchThresholdsRemaining;
|
private boolean myAdditionalPrefetchThresholdsRemaining;
|
||||||
private List<JpaPid> myPreviouslyAddedResourcePids;
|
private List<JpaPid> myPreviouslyAddedResourcePids;
|
||||||
private Integer myMaxResultsToFetch;
|
private Integer myMaxResultsToFetch;
|
||||||
|
|
||||||
private final Consumer<String> myOnRemove;
|
|
||||||
|
|
||||||
private final int mySyncSize;
|
|
||||||
private final Integer myLoadingThrottleForUnitTests;
|
|
||||||
|
|
||||||
// injected beans
|
|
||||||
protected final HapiTransactionService myTxService;
|
|
||||||
protected final FhirContext myContext;
|
|
||||||
private final IInterceptorBroadcaster myInterceptorBroadcaster;
|
|
||||||
private final SearchBuilderFactory<JpaPid> mySearchBuilderFactory;
|
|
||||||
protected final ISearchResultCacheSvc mySearchResultCacheSvc;
|
|
||||||
private final JpaStorageSettings myStorageSettings;
|
|
||||||
private final ISearchCacheSvc mySearchCacheSvc;
|
|
||||||
private final IPagingProvider myPagingProvider;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@ -169,6 +164,10 @@ public class SearchTask implements Callable<Void> {
|
||||||
myParentTransaction = ElasticApm.currentTransaction();
|
myParentTransaction = ElasticApm.currentTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected RequestPartitionId getRequestPartitionId() {
|
||||||
|
return myRequestPartitionId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called by the server HTTP thread, and
|
* This method is called by the server HTTP thread, and
|
||||||
* will block until at least one page of results have been
|
* will block until at least one page of results have been
|
||||||
|
@ -267,11 +266,18 @@ public class SearchTask implements Callable<Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveSearch() {
|
public void saveSearch() {
|
||||||
myTxService.execute(myRequest, null, Propagation.REQUIRES_NEW, Isolation.DEFAULT, ()->doSaveSearch());
|
myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(myRequestPartitionId)
|
||||||
|
.withPropagation(Propagation.REQUIRES_NEW)
|
||||||
|
.execute(() -> doSaveSearch());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveUnsynced(final IResultIterator theResultIter) {
|
private void saveUnsynced(final IResultIterator theResultIter) {
|
||||||
myTxService.withRequest(myRequest).execute(()->{
|
myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(myRequestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
if (mySearch.getId() == null) {
|
if (mySearch.getId() == null) {
|
||||||
doSaveSearch();
|
doSaveSearch();
|
||||||
}
|
}
|
||||||
|
@ -303,7 +309,7 @@ public class SearchTask implements Callable<Void> {
|
||||||
// Actually store the results in the query cache storage
|
// Actually store the results in the query cache storage
|
||||||
myCountSavedTotal += unsyncedPids.size();
|
myCountSavedTotal += unsyncedPids.size();
|
||||||
myCountSavedThisPass += unsyncedPids.size();
|
myCountSavedThisPass += unsyncedPids.size();
|
||||||
mySearchResultCacheSvc.storeResults(mySearch, mySyncedPids, unsyncedPids);
|
mySearchResultCacheSvc.storeResults(mySearch, mySyncedPids, unsyncedPids, myRequest, getRequestPartitionId());
|
||||||
|
|
||||||
synchronized (mySyncedPids) {
|
synchronized (mySyncedPids) {
|
||||||
int numSyncedThisPass = unsyncedPids.size();
|
int numSyncedThisPass = unsyncedPids.size();
|
||||||
|
@ -387,7 +393,11 @@ public class SearchTask implements Callable<Void> {
|
||||||
// Create an initial search in the DB and give it an ID
|
// Create an initial search in the DB and give it an ID
|
||||||
saveSearch();
|
saveSearch();
|
||||||
|
|
||||||
myTxService.execute(myRequest, null, Propagation.REQUIRED, Isolation.READ_COMMITTED, ()->doSearch());
|
myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(myRequestPartitionId)
|
||||||
|
.withIsolation(Isolation.READ_COMMITTED)
|
||||||
|
.execute(() -> doSearch());
|
||||||
|
|
||||||
mySearchRuntimeDetails.setSearchStatus(mySearch.getStatus());
|
mySearchRuntimeDetails.setSearchStatus(mySearch.getStatus());
|
||||||
if (mySearch.getStatus() == SearchStatusEnum.FINISHED) {
|
if (mySearch.getStatus() == SearchStatusEnum.FINISHED) {
|
||||||
|
@ -506,7 +516,10 @@ public class SearchTask implements Callable<Void> {
|
||||||
|
|
||||||
ourLog.trace("Got count {}", count);
|
ourLog.trace("Got count {}", count);
|
||||||
|
|
||||||
myTxService.withRequest(myRequest).execute(()->{
|
myTxService
|
||||||
|
.withRequest(myRequest)
|
||||||
|
.withRequestPartitionId(myRequestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
mySearch.setTotalCount(count.intValue());
|
mySearch.setTotalCount(count.intValue());
|
||||||
if (myParamWantOnlyCount) {
|
if (myParamWantOnlyCount) {
|
||||||
mySearch.setStatus(SearchStatusEnum.FINISHED);
|
mySearch.setStatus(SearchStatusEnum.FINISHED);
|
||||||
|
|
|
@ -111,7 +111,6 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.NEVER)
|
|
||||||
public Optional<Search> tryToMarkSearchAsInProgress(Search theSearch) {
|
public Optional<Search> tryToMarkSearchAsInProgress(Search theSearch) {
|
||||||
ourLog.trace("Going to try to change search status from {} to {}", theSearch.getStatus(), SearchStatusEnum.LOADING);
|
ourLog.trace("Going to try to change search status from {} to {}", theSearch.getStatus(), SearchStatusEnum.LOADING);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -20,17 +20,18 @@ package ca.uhn.fhir.jpa.search.cache;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchResult;
|
import ca.uhn.fhir.jpa.entity.SearchResult;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -43,9 +44,15 @@ public class DatabaseSearchResultCacheSvcImpl implements ISearchResultCacheSvc {
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISearchResultDao mySearchResultDao;
|
private ISearchResultDao mySearchResultDao;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IHapiTransactionService myTransactionService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
public List<JpaPid> fetchResultPids(Search theSearch, int theFrom, int theTo, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
|
||||||
public List<JpaPid> fetchResultPids(Search theSearch, int theFrom, int theTo) {
|
return myTransactionService
|
||||||
|
.withRequest(theRequestDetails)
|
||||||
|
.withRequestPartitionId(theRequestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
final Pageable page = toPage(theFrom, theTo);
|
final Pageable page = toPage(theFrom, theTo);
|
||||||
if (page == null) {
|
if (page == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
@ -58,19 +65,27 @@ public class DatabaseSearchResultCacheSvcImpl implements ISearchResultCacheSvc {
|
||||||
ourLog.debug("fetchResultPids for range {}-{} returned {} pids", theFrom, theTo, retVal.size());
|
ourLog.debug("fetchResultPids for range {}-{} returned {} pids", theFrom, theTo, retVal.size());
|
||||||
|
|
||||||
return JpaPid.fromLongList(retVal);
|
return JpaPid.fromLongList(retVal);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
public List<JpaPid> fetchAllResultPids(Search theSearch, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
|
||||||
public List<JpaPid> fetchAllResultPids(Search theSearch) {
|
return myTransactionService
|
||||||
|
.withRequest(theRequestDetails)
|
||||||
|
.withRequestPartitionId(theRequestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
List<Long> retVal = mySearchResultDao.findWithSearchPidOrderIndependent(theSearch.getId());
|
List<Long> retVal = mySearchResultDao.findWithSearchPidOrderIndependent(theSearch.getId());
|
||||||
ourLog.trace("fetchAllResultPids returned {} pids", retVal.size());
|
ourLog.trace("fetchAllResultPids returned {} pids", retVal.size());
|
||||||
return JpaPid.fromLongList(retVal);
|
return JpaPid.fromLongList(retVal);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
public void storeResults(Search theSearch, List<JpaPid> thePreviouslyStoredResourcePids, List<JpaPid> theNewResourcePids, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
|
||||||
public void storeResults(Search theSearch, List<JpaPid> thePreviouslyStoredResourcePids, List<JpaPid> theNewResourcePids) {
|
myTransactionService
|
||||||
|
.withRequest(theRequestDetails)
|
||||||
|
.withRequestPartitionId(theRequestPartitionId)
|
||||||
|
.execute(() -> {
|
||||||
List<SearchResult> resultsToSave = Lists.newArrayList();
|
List<SearchResult> resultsToSave = Lists.newArrayList();
|
||||||
|
|
||||||
ourLog.debug("Storing {} results with {} previous for search", theNewResourcePids.size(), thePreviouslyStoredResourcePids.size());
|
ourLog.debug("Storing {} results with {} previous for search", theNewResourcePids.size(), thePreviouslyStoredResourcePids.size());
|
||||||
|
@ -87,6 +102,7 @@ public class DatabaseSearchResultCacheSvcImpl implements ISearchResultCacheSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
mySearchResultDao.saveAll(resultsToSave);
|
mySearchResultDao.saveAll(resultsToSave);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.search.cache;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -30,12 +32,12 @@ public interface ISearchResultCacheSvc {
|
||||||
/**
|
/**
|
||||||
* @param theSearch The search - This method is not required to persist any chances to the Search object, it is only provided here for identification
|
* @param theSearch The search - This method is not required to persist any chances to the Search object, it is only provided here for identification
|
||||||
* @param thePreviouslyStoredResourcePids A list of resource PIDs that have previously been saved to this search
|
* @param thePreviouslyStoredResourcePids A list of resource PIDs that have previously been saved to this search
|
||||||
* @param theNewResourcePids A list of new resoure PIDs to add to this search (these ones have not been previously saved)
|
* @param theNewResourcePids A list of new resource PIDs to add to this search (these ones have not been previously saved)
|
||||||
*/
|
*/
|
||||||
void storeResults(Search theSearch, List<JpaPid> thePreviouslyStoredResourcePids, List<JpaPid> theNewResourcePids);
|
void storeResults(Search theSearch, List<JpaPid> thePreviouslyStoredResourcePids, List<JpaPid> theNewResourcePids, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a sunset of the search result IDs from the cache
|
* Fetch a subset of the search result IDs from the cache
|
||||||
*
|
*
|
||||||
* @param theSearch The search to fetch IDs for
|
* @param theSearch The search to fetch IDs for
|
||||||
* @param theFrom The starting index (inclusive)
|
* @param theFrom The starting index (inclusive)
|
||||||
|
@ -44,7 +46,7 @@ public interface ISearchResultCacheSvc {
|
||||||
* have been removed from the cache for some reason, such as expiry or manual purge)
|
* have been removed from the cache for some reason, such as expiry or manual purge)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
List<JpaPid> fetchResultPids(Search theSearch, int theFrom, int theTo);
|
List<JpaPid> fetchResultPids(Search theSearch, int theFrom, int theTo, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all result PIDs for a given search with no particular order required
|
* Fetch all result PIDs for a given search with no particular order required
|
||||||
|
@ -54,6 +56,6 @@ public interface ISearchResultCacheSvc {
|
||||||
* have been removed from the cache for some reason, such as expiry or manual purge)
|
* have been removed from the cache for some reason, such as expiry or manual purge)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
List<JpaPid> fetchAllResultPids(Search theSearch);
|
List<JpaPid> fetchAllResultPids(Search theSearch, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package ca.uhn.fhir.jpa.util;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
|
import org.springframework.orm.jpa.JpaDialect;
|
||||||
|
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||||
|
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||||
|
|
||||||
|
public class JpaHapiTransactionService extends HapiTransactionService {
|
||||||
|
|
||||||
|
private volatile Boolean myCustomIsolationSupported;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCustomIsolationSupported() {
|
||||||
|
if (myCustomIsolationSupported == null) {
|
||||||
|
if (myTransactionManager instanceof JpaTransactionManager) {
|
||||||
|
JpaDialect jpaDialect = ((JpaTransactionManager) myTransactionManager).getJpaDialect();
|
||||||
|
myCustomIsolationSupported = (jpaDialect instanceof HibernateJpaDialect);
|
||||||
|
} else {
|
||||||
|
myCustomIsolationSupported = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return myCustomIsolationSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||||
import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
|
import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
|
||||||
import ca.uhn.fhir.mdm.provider.MdmControllerUtil;
|
import ca.uhn.fhir.mdm.provider.MdmControllerUtil;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
@ -125,9 +124,8 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<MdmLinkJson> queryLinks(MdmQuerySearchParameters theMdmQuerySearchParameters, MdmTransactionContext theMdmTransactionContext, RequestDetails theRequestDetails)
|
public Page<MdmLinkJson> queryLinks(MdmQuerySearchParameters theMdmQuerySearchParameters, MdmTransactionContext theMdmTransactionContext, RequestDetails theRequestDetails) {
|
||||||
{
|
RequestPartitionId theReadPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null);
|
||||||
RequestPartitionId theReadPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, null);
|
|
||||||
Page<MdmLinkJson> resultPage;
|
Page<MdmLinkJson> resultPage;
|
||||||
if (theReadPartitionId.hasPartitionIds()) {
|
if (theReadPartitionId.hasPartitionIds()) {
|
||||||
theMdmQuerySearchParameters.setPartitionIds(theReadPartitionId.getPartitionIds());
|
theMdmQuerySearchParameters.setPartitionIds(theReadPartitionId.getPartitionIds());
|
||||||
|
@ -166,7 +164,7 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
|
||||||
@Override
|
@Override
|
||||||
public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, RequestDetails theRequestDetails, String theRequestResourceType) {
|
public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, RequestDetails theRequestDetails, String theRequestResourceType) {
|
||||||
Page<MdmLinkJson> resultPage;
|
Page<MdmLinkJson> resultPage;
|
||||||
RequestPartitionId readPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, null);
|
RequestPartitionId readPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null);
|
||||||
|
|
||||||
if (readPartitionId.isAllPartitions()) {
|
if (readPartitionId.isAllPartitions()) {
|
||||||
resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, null, theRequestResourceType);
|
resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, null, theRequestResourceType);
|
||||||
|
@ -209,8 +207,8 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
|
||||||
params.setBatchSize(theBatchSize.getValue().intValue());
|
params.setBatchSize(theBatchSize.getValue().intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadPartitionIdRequestDetails details= new ReadPartitionIdRequestDetails(null, RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, null, null, null);
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_MDM_CLEAR);
|
||||||
RequestPartitionId requestPartition = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, details);
|
RequestPartitionId requestPartition = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
|
||||||
params.setRequestPartitionId(requestPartition);
|
params.setRequestPartitionId(requestPartition);
|
||||||
|
|
||||||
JobInstanceStartRequest request = new JobInstanceStartRequest();
|
JobInstanceStartRequest request = new JobInstanceStartRequest();
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatch() throws Exception {
|
public void testMatch() {
|
||||||
Patient jane = buildJanePatient();
|
Patient jane = buildJanePatient();
|
||||||
jane.setActive(true);
|
jane.setActive(true);
|
||||||
Patient createdJane = createPatient(jane);
|
Patient createdJane = createPatient(jane);
|
||||||
|
|
|
@ -32,6 +32,8 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentMatcher;
|
import org.mockito.ArgumentMatcher;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
|
@ -41,6 +43,7 @@ import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ca.uhn.fhir.mdm.provider.MdmProviderDstu3Plus.DEFAULT_PAGE_SIZE;
|
import static ca.uhn.fhir.mdm.provider.MdmProviderDstu3Plus.DEFAULT_PAGE_SIZE;
|
||||||
import static ca.uhn.fhir.mdm.provider.MdmProviderDstu3Plus.MAX_PAGE_SIZE;
|
import static ca.uhn.fhir.mdm.provider.MdmProviderDstu3Plus.MAX_PAGE_SIZE;
|
||||||
|
@ -50,6 +53,8 @@ import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
|
||||||
public class MdmControllerSvcImplTest extends BaseLinkR4Test {
|
public class MdmControllerSvcImplTest extends BaseLinkR4Test {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(MdmControllerSvcImplTest.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
IMdmControllerSvc myMdmControllerSvc;
|
IMdmControllerSvc myMdmControllerSvc;
|
||||||
|
|
||||||
|
@ -137,13 +142,18 @@ public class MdmControllerSvcImplTest extends BaseLinkR4Test {
|
||||||
assertEquals(MdmLinkSourceEnum.AUTO, link.getLinkSource());
|
assertEquals(MdmLinkSourceEnum.AUTO, link.getLinkSource());
|
||||||
assertLinkCount(2);
|
assertLinkCount(2);
|
||||||
|
|
||||||
|
runInTransaction(()->{
|
||||||
|
ourLog.info("Links: {}", myMdmLinkDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
|
||||||
|
});
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
Page<MdmLinkJson> resultPage = myMdmControllerSvc.getDuplicateGoldenResources(null,
|
Page<MdmLinkJson> resultPage = myMdmControllerSvc.getDuplicateGoldenResources(null,
|
||||||
new MdmPageRequest((Integer) null, null, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE),
|
new MdmPageRequest((Integer) null, null, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE),
|
||||||
new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.fromPartitionId(1)), null);
|
new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.fromPartitionId(1)), null);
|
||||||
|
myCaptureQueriesListener.logSelectQueries();
|
||||||
|
|
||||||
assertEquals(resultPage.getContent().size(), 1);
|
assertEquals(1, resultPage.getContent().size());
|
||||||
|
assertEquals(patient.getIdElement().getResourceType() + "/" + patient.getIdElement().getIdPart(), resultPage.getContent().get(0).getSourceId());
|
||||||
assertEquals(resultPage.getContent().get(0).getSourceId(), patient.getIdElement().getResourceType() + "/" + patient.getIdElement().getIdPart());
|
|
||||||
|
|
||||||
Mockito.verify(myRequestPartitionHelperSvc, Mockito.atLeastOnce()).validateHasPartitionPermissions(any(), eq("Patient"), argThat(new PartitionIdMatcher(requestPartitionId)));
|
Mockito.verify(myRequestPartitionHelperSvc, Mockito.atLeastOnce()).validateHasPartitionPermissions(any(), eq("Patient"), argThat(new PartitionIdMatcher(requestPartitionId)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,25 @@ public class PartitionSettings {
|
||||||
private boolean myIncludePartitionInSearchHashes = false;
|
private boolean myIncludePartitionInSearchHashes = false;
|
||||||
private boolean myUnnamedPartitionMode;
|
private boolean myUnnamedPartitionMode;
|
||||||
private Integer myDefaultPartitionId;
|
private Integer myDefaultPartitionId;
|
||||||
|
private boolean myAlwaysOpenNewTransactionForDifferentPartition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we always open a new database transaction if the partition context changes
|
||||||
|
*
|
||||||
|
* @since 6.6.0
|
||||||
|
*/
|
||||||
|
public boolean isAlwaysOpenNewTransactionForDifferentPartition() {
|
||||||
|
return myAlwaysOpenNewTransactionForDifferentPartition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we always open a new database transaction if the partition context changes
|
||||||
|
*
|
||||||
|
* @since 6.6.0
|
||||||
|
*/
|
||||||
|
public void setAlwaysOpenNewTransactionForDifferentPartition(boolean theAlwaysOpenNewTransactionForDifferentPartition) {
|
||||||
|
myAlwaysOpenNewTransactionForDifferentPartition = theAlwaysOpenNewTransactionForDifferentPartition;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to <code>true</code> (default is <code>false</code>) the <code>PARTITION_ID</code> value will be factored into the
|
* If set to <code>true</code> (default is <code>false</code>) the <code>PARTITION_ID</code> value will be factored into the
|
||||||
|
@ -136,6 +155,12 @@ public class PartitionSettings {
|
||||||
myDefaultPartitionId = theDefaultPartitionId;
|
myDefaultPartitionId = theDefaultPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled the JPA server will allow unqualified cross partition reference
|
||||||
|
*/
|
||||||
|
public boolean isAllowUnqualifiedCrossPartitionReference() {
|
||||||
|
return myAllowReferencesAcrossPartitions.equals(PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
public enum CrossPartitionReferenceMode {
|
public enum CrossPartitionReferenceMode {
|
||||||
|
|
||||||
|
@ -145,18 +170,11 @@ public class PartitionSettings {
|
||||||
NOT_ALLOWED,
|
NOT_ALLOWED,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* References can cross partition boundaries, in a way that hides the existence of partitions to the end user
|
* References can cross partition boundaries, with an assumption that boundaries
|
||||||
|
* will be managed by the database.
|
||||||
*/
|
*/
|
||||||
ALLOWED_UNQUALIFIED
|
ALLOWED_UNQUALIFIED,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If enabled the JPA server will allow unqualified cross partition reference
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public boolean isAllowUnqualifiedCrossPartitionReference() {
|
|
||||||
return myAllowReferencesAcrossPartitions.equals(PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,10 @@ package ca.uhn.fhir.jpa.model.cross;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
public interface IBasePersistedResource extends IResourceLookup {
|
public interface IBasePersistedResource<T extends IResourcePersistentId<?>> extends IResourceLookup<T> {
|
||||||
|
|
||||||
IIdType getIdDt();
|
IIdType getIdDt();
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public interface IResourceLookup<T extends IResourcePersistentId> {
|
public interface IResourceLookup<T extends IResourcePersistentId<?>> {
|
||||||
String getResourceType();
|
String getResourceType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -65,8 +65,12 @@ public class HapiSequenceStyleGenerator implements IdentifierGenerator, Persiste
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Serializable generate(SharedSessionContractImplementor theSession, Object theObject) throws HibernateException {
|
public Serializable generate(SharedSessionContractImplementor theSession, Object theObject) throws HibernateException {
|
||||||
|
Long retVal = myIdMassager.generate(myGeneratorName);
|
||||||
|
if (retVal == null) {
|
||||||
Long next = (Long) myGen.generate(theSession, theObject);
|
Long next = (Long) myGen.generate(theSession, theObject);
|
||||||
return myIdMassager.massage(myGeneratorName, next);
|
retVal = myIdMassager.massage(myGeneratorName, next);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.dialect;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an internal API and may change or disappear without notice
|
* This is an internal API and may change or disappear without notice
|
||||||
*
|
*
|
||||||
|
@ -29,6 +31,17 @@ public interface ISequenceValueMassager {
|
||||||
|
|
||||||
Long massage(String theGeneratorName, Long theId);
|
Long massage(String theGeneratorName, Long theId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If desired, the massager can supply an ID on its own. This method is tried first,
|
||||||
|
* and if it returns null, the normal hi-lo generator is called and then
|
||||||
|
* {@link #massage(String, Long)} is called on the output of that.
|
||||||
|
*
|
||||||
|
* @return Returns an ID or null
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
default Long generate(String theGeneratorName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
final class NoopSequenceValueMassager implements ISequenceValueMassager {
|
final class NoopSequenceValueMassager implements ISequenceValueMassager {
|
||||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||||
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import org.hibernate.annotations.OptimisticLock;
|
import org.hibernate.annotations.OptimisticLock;
|
||||||
|
|
||||||
|
@ -38,8 +39,10 @@ import java.util.Date;
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
|
|
||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
public abstract class BaseHasResource extends BasePartitionable implements IBaseResourceEntity, IBasePersistedResource {
|
public abstract class BaseHasResource extends BasePartitionable implements IBaseResourceEntity, IBasePersistedResource<JpaPid> {
|
||||||
|
|
||||||
|
public static final String RES_PUBLISHED = "RES_PUBLISHED";
|
||||||
|
public static final String RES_UPDATED = "RES_UPDATED";
|
||||||
@Column(name = "RES_DELETED_AT", nullable = true)
|
@Column(name = "RES_DELETED_AT", nullable = true)
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
private Date myDeleted;
|
private Date myDeleted;
|
||||||
|
@ -54,12 +57,12 @@ public abstract class BaseHasResource extends BasePartitionable implements IBase
|
||||||
private boolean myHasTags;
|
private boolean myHasTags;
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
@Column(name = "RES_PUBLISHED", nullable = false)
|
@Column(name = RES_PUBLISHED, nullable = false)
|
||||||
@OptimisticLock(excluded = true)
|
@OptimisticLock(excluded = true)
|
||||||
private Date myPublished;
|
private Date myPublished;
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
@Column(name = "RES_UPDATED", nullable = false)
|
@Column(name = RES_UPDATED, nullable = false)
|
||||||
@OptimisticLock(excluded = true)
|
@OptimisticLock(excluded = true)
|
||||||
private Date myUpdated;
|
private Date myUpdated;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
|
|
||||||
|
@ -118,4 +119,14 @@ public class PartitionablePartitionId implements Cloneable {
|
||||||
return RequestPartitionId.defaultPartition();
|
return RequestPartitionId.defaultPartition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static PartitionablePartitionId toStoragePartition(@Nonnull RequestPartitionId theRequestPartitionId, @Nonnull PartitionSettings thePartitionSettings) {
|
||||||
|
Integer partitionId = theRequestPartitionId.getFirstPartitionIdOrNull();
|
||||||
|
if (partitionId == null) {
|
||||||
|
partitionId = thePartitionSettings.getDefaultPartitionId();
|
||||||
|
}
|
||||||
|
return new PartitionablePartitionId(partitionId, theRequestPartitionId.getPartitionDate());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,6 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ResourceHistoryTable#ENCODING_COL_LENGTH
|
|
||||||
*/
|
|
||||||
public enum ResourceEncodingEnum {
|
public enum ResourceEncodingEnum {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -47,7 +44,13 @@ public enum ResourceEncodingEnum {
|
||||||
/**
|
/**
|
||||||
* Resource was deleted - No contents expected
|
* Resource was deleted - No contents expected
|
||||||
*/
|
*/
|
||||||
DEL;
|
DEL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Externally stored resource - Resource text is a reference to an external storage location,
|
||||||
|
* which will be stored in {@link ResourceHistoryTable#getResourceTextVc()}
|
||||||
|
*/
|
||||||
|
ESR;
|
||||||
|
|
||||||
public IParser newParser(FhirContext theContext) {
|
public IParser newParser(FhirContext theContext) {
|
||||||
return theContext.newJsonParser();
|
return theContext.newJsonParser();
|
||||||
|
|
|
@ -92,6 +92,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
|
||||||
@Column(name = "RES_TEXT_VC", length = RES_TEXT_VC_MAX_LENGTH, nullable = true)
|
@Column(name = "RES_TEXT_VC", length = RES_TEXT_VC_MAX_LENGTH, nullable = true)
|
||||||
@OptimisticLock(excluded = true)
|
@OptimisticLock(excluded = true)
|
||||||
private String myResourceTextVc;
|
private String myResourceTextVc;
|
||||||
|
|
||||||
@Column(name = "RES_ENCODING", nullable = false, length = ENCODING_COL_LENGTH)
|
@Column(name = "RES_ENCODING", nullable = false, length = ENCODING_COL_LENGTH)
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@OptimisticLock(excluded = true)
|
@OptimisticLock(excluded = true)
|
||||||
|
|
|
@ -98,6 +98,9 @@ public class ResourceLink extends BaseResourceIndex {
|
||||||
@Transient
|
@Transient
|
||||||
private transient String myTargetResourceId;
|
private transient String myTargetResourceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
public ResourceLink() {
|
public ResourceLink() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,16 +78,18 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Indexed(routingBinder= @RoutingBinderRef(type = ResourceTableRoutingBinder.class))
|
@Indexed(routingBinder= @RoutingBinderRef(type = ResourceTableRoutingBinder.class))
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes = {
|
@Table(name = ResourceTable.HFJ_RESOURCE, uniqueConstraints = {}, indexes = {
|
||||||
// Do not reuse previously used index name: IDX_INDEXSTATUS, IDX_RES_TYPE
|
// Do not reuse previously used index name: IDX_INDEXSTATUS, IDX_RES_TYPE
|
||||||
@Index(name = "IDX_RES_DATE", columnList = "RES_UPDATED"),
|
@Index(name = "IDX_RES_DATE", columnList = BaseHasResource.RES_UPDATED),
|
||||||
@Index(name = "IDX_RES_TYPE_DEL_UPDATED", columnList = "RES_TYPE,RES_DELETED_AT,RES_UPDATED,PARTITION_ID,RES_ID"),
|
@Index(name = "IDX_RES_TYPE_DEL_UPDATED", columnList = "RES_TYPE,RES_DELETED_AT,RES_UPDATED,PARTITION_ID,RES_ID"),
|
||||||
})
|
})
|
||||||
@NamedEntityGraph(name = "Resource.noJoins")
|
@NamedEntityGraph(name = "Resource.noJoins")
|
||||||
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource, IResourceLookup {
|
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource<JpaPid> {
|
||||||
public static final int RESTYPE_LEN = 40;
|
public static final int RESTYPE_LEN = 40;
|
||||||
private static final int MAX_LANGUAGE_LENGTH = 20;
|
private static final int MAX_LANGUAGE_LENGTH = 20;
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
public static final String HFJ_RESOURCE = "HFJ_RESOURCE";
|
||||||
|
public static final String RES_TYPE = "RES_TYPE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
|
* Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
|
||||||
|
@ -263,7 +265,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
@OptimisticLock(excluded = true)
|
@OptimisticLock(excluded = true)
|
||||||
private Collection<ResourceLink> myResourceLinksAsTarget;
|
private Collection<ResourceLink> myResourceLinksAsTarget;
|
||||||
|
|
||||||
@Column(name = "RES_TYPE", length = RESTYPE_LEN, nullable = false)
|
@Column(name = RES_TYPE, length = RESTYPE_LEN, nullable = false)
|
||||||
@FullTextField
|
@FullTextField
|
||||||
@OptimisticLock(excluded = true)
|
@OptimisticLock(excluded = true)
|
||||||
private String myResourceType;
|
private String myResourceType;
|
||||||
|
@ -769,7 +771,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IResourcePersistentId getPersistentId() {
|
public JpaPid getPersistentId() {
|
||||||
return JpaPid.fromId(getId());
|
return JpaPid.fromId(getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,10 @@ import java.util.Set;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains configuration options common to all hapi-fhir-storage implementations.
|
||||||
|
* Ultimately it should live in that project
|
||||||
|
*/
|
||||||
public class StorageSettings {
|
public class StorageSettings {
|
||||||
/**
|
/**
|
||||||
* @since 5.6.0
|
* @since 5.6.0
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.interceptor.model;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR - Core Library
|
* HAPI FHIR Search Parameters
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
@ -20,31 +20,52 @@ package ca.uhn.fhir.interceptor.model;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a model class used as a parameter for the interceptor pointcut
|
||||||
|
* {@link ca.uhn.fhir.interceptor.api.Pointcut#STORAGE_PARTITION_IDENTIFY_READ}.
|
||||||
|
*/
|
||||||
public class ReadPartitionIdRequestDetails extends PartitionIdRequestDetails {
|
public class ReadPartitionIdRequestDetails extends PartitionIdRequestDetails {
|
||||||
|
|
||||||
private final String myResourceType;
|
private final String myResourceType;
|
||||||
private final RestOperationTypeEnum myRestOperationType;
|
private final RestOperationTypeEnum myRestOperationType;
|
||||||
private final IIdType myReadResourceId;
|
private final IIdType myReadResourceId;
|
||||||
private final Object mySearchParams;
|
@Nullable
|
||||||
|
private final SearchParameterMap mySearchParams;
|
||||||
|
@Nullable
|
||||||
private final IBaseResource myConditionalTargetOrNull;
|
private final IBaseResource myConditionalTargetOrNull;
|
||||||
|
@Nullable
|
||||||
|
private final String mySearchUuid;
|
||||||
|
@Nullable
|
||||||
|
private final String myExtendedOperationName;
|
||||||
|
|
||||||
public ReadPartitionIdRequestDetails(String theResourceType, RestOperationTypeEnum theRestOperationType, IIdType theReadResourceId, Object theSearchParams, @Nullable IBaseResource theConditionalTargetOrNull) {
|
private ReadPartitionIdRequestDetails(String theResourceType, RestOperationTypeEnum theRestOperationType, IIdType theReadResourceId, @Nullable SearchParameterMap theSearchParams, @Nullable IBaseResource theConditionalTargetOrNull, @Nullable String theSearchUuid, String theExtendedOperationName) {
|
||||||
myResourceType = theResourceType;
|
myResourceType = theResourceType;
|
||||||
myRestOperationType = theRestOperationType;
|
myRestOperationType = theRestOperationType;
|
||||||
myReadResourceId = theReadResourceId;
|
myReadResourceId = theReadResourceId;
|
||||||
mySearchParams = theSearchParams;
|
mySearchParams = theSearchParams;
|
||||||
myConditionalTargetOrNull = theConditionalTargetOrNull;
|
myConditionalTargetOrNull = theConditionalTargetOrNull;
|
||||||
|
mySearchUuid = theSearchUuid;
|
||||||
|
myExtendedOperationName = theExtendedOperationName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReadPartitionIdRequestDetails forRead(String theResourceType, IIdType theId, boolean theIsVread) {
|
@Nullable
|
||||||
RestOperationTypeEnum op = theIsVread ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
|
public String getExtendedOperationName() {
|
||||||
return new ReadPartitionIdRequestDetails(theResourceType, op, theId.withResourceType(theResourceType), null, null);
|
return myExtendedOperationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getSearchUuid() {
|
||||||
|
return mySearchUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getResourceType() {
|
public String getResourceType() {
|
||||||
|
@ -59,18 +80,45 @@ public class ReadPartitionIdRequestDetails extends PartitionIdRequestDetails {
|
||||||
return myReadResourceId;
|
return myReadResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getSearchParams() {
|
@Nullable
|
||||||
|
public SearchParameterMap getSearchParams() {
|
||||||
return mySearchParams;
|
return mySearchParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public IBaseResource getConditionalTargetOrNull() {
|
public IBaseResource getConditionalTargetOrNull() {
|
||||||
return myConditionalTargetOrNull;
|
return myConditionalTargetOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param theId The resource ID (must include a resource type and ID)
|
||||||
|
*/
|
||||||
|
public static ReadPartitionIdRequestDetails forRead(IIdType theId) {
|
||||||
|
assert isNotBlank(theId.getResourceType());
|
||||||
|
assert isNotBlank(theId.getIdPart());
|
||||||
|
return forRead(theId.getResourceType(), theId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReadPartitionIdRequestDetails forOperation(@Nullable String theResourceType, @Nullable IIdType theId, @Nonnull String theExtendedOperationName) {
|
||||||
|
RestOperationTypeEnum op;
|
||||||
|
if (theId != null) {
|
||||||
|
op = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
|
||||||
|
} else if (theResourceType != null) {
|
||||||
|
op = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
|
||||||
|
} else {
|
||||||
|
op = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
public static ReadPartitionIdRequestDetails forSearchType(String theResourceType, Object theParams, IBaseResource theConditionalOperationTargetOrNull) {
|
return new ReadPartitionIdRequestDetails(theResourceType, op, null, null, null, null, theExtendedOperationName);
|
||||||
return new ReadPartitionIdRequestDetails(theResourceType, RestOperationTypeEnum.SEARCH_TYPE, null, theParams, theConditionalOperationTargetOrNull);
|
}
|
||||||
|
|
||||||
|
public static ReadPartitionIdRequestDetails forRead(String theResourceType, @Nonnull IIdType theId, boolean theIsVread) {
|
||||||
|
RestOperationTypeEnum op = theIsVread ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
|
||||||
|
return new ReadPartitionIdRequestDetails(theResourceType, op, theId.withResourceType(theResourceType), null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReadPartitionIdRequestDetails forSearchType(String theResourceType, SearchParameterMap theParams, IBaseResource theConditionalOperationTargetOrNull) {
|
||||||
|
return new ReadPartitionIdRequestDetails(theResourceType, RestOperationTypeEnum.SEARCH_TYPE, null, theParams, theConditionalOperationTargetOrNull, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReadPartitionIdRequestDetails forHistory(String theResourceType, IIdType theIdType) {
|
public static ReadPartitionIdRequestDetails forHistory(String theResourceType, IIdType theIdType) {
|
||||||
|
@ -82,6 +130,10 @@ public class ReadPartitionIdRequestDetails extends PartitionIdRequestDetails {
|
||||||
} else {
|
} else {
|
||||||
restOperationTypeEnum = RestOperationTypeEnum.HISTORY_SYSTEM;
|
restOperationTypeEnum = RestOperationTypeEnum.HISTORY_SYSTEM;
|
||||||
}
|
}
|
||||||
return new ReadPartitionIdRequestDetails(theResourceType, restOperationTypeEnum, theIdType, null, null);
|
return new ReadPartitionIdRequestDetails(theResourceType, restOperationTypeEnum, theIdType, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReadPartitionIdRequestDetails forSearchUuid(String theUuid) {
|
||||||
|
return new ReadPartitionIdRequestDetails(null, RestOperationTypeEnum.GET_PAGE, null, null, null, theUuid, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,8 +28,8 @@ import javax.annotation.Nonnull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface is used by the {@link IResourceChangeListenerCacheRefresher} to read resources matching the provided
|
* This interface is used by the {@literal IResourceChangeListenerCacheRefresher} to read resources matching the provided
|
||||||
* search parameter map in the repository and compare them to caches stored in the {@link IResourceChangeListenerRegistry}.
|
* search parameter map in the repository and compare them to caches stored in the {@literal IResourceChangeListenerRegistry}.
|
||||||
*/
|
*/
|
||||||
public interface IResourceVersionSvc {
|
public interface IResourceVersionSvc {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.cache;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
@ -44,7 +44,7 @@ public class ResourceVersionMap {
|
||||||
private final Map<IIdType, Long> myMap = new HashMap<>();
|
private final Map<IIdType, Long> myMap = new HashMap<>();
|
||||||
private ResourceVersionMap() {}
|
private ResourceVersionMap() {}
|
||||||
|
|
||||||
public static ResourceVersionMap fromResourceTableEntities(List<ResourceTable> theEntities) {
|
public static ResourceVersionMap fromResourceTableEntities(List<? extends IBasePersistedResource> theEntities) {
|
||||||
ResourceVersionMap retval = new ResourceVersionMap();
|
ResourceVersionMap retval = new ResourceVersionMap();
|
||||||
theEntities.forEach(entity -> retval.add(entity.getIdDt()));
|
theEntities.forEach(entity -> retval.add(entity.getIdDt()));
|
||||||
return retval;
|
return retval;
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Storage api
|
* HAPI FHIR Search Parameters
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
|
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
@ -35,18 +34,18 @@ import java.util.Set;
|
||||||
public interface IRequestPartitionHelperSvc {
|
public interface IRequestPartitionHelperSvc {
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType, ReadPartitionIdRequestDetails theDetails);
|
RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, ReadPartitionIdRequestDetails theDetails);
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
default RequestPartitionId determineReadPartitionForRequestForRead(RequestDetails theRequest, String theResourceType, IIdType theId) {
|
default RequestPartitionId determineReadPartitionForRequestForRead(RequestDetails theRequest, String theResourceType, @Nonnull IIdType theId) {
|
||||||
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forRead(theResourceType, theId, theId.hasVersionIdPart());
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forRead(theResourceType, theId, theId.hasVersionIdPart());
|
||||||
return determineReadPartitionForRequest(theRequest, theResourceType, details);
|
return determineReadPartitionForRequest(theRequest, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
default RequestPartitionId determineReadPartitionForRequestForSearchType(RequestDetails theRequest, String theResourceType, SearchParameterMap theParams, IBaseResource theConditionalOperationTargetOrNull) {
|
default RequestPartitionId determineReadPartitionForRequestForSearchType(RequestDetails theRequest, String theResourceType, SearchParameterMap theParams, IBaseResource theConditionalOperationTargetOrNull) {
|
||||||
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType(theResourceType, theParams, theConditionalOperationTargetOrNull);
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType(theResourceType, theParams, theConditionalOperationTargetOrNull);
|
||||||
return determineReadPartitionForRequest(theRequest, theResourceType, details);
|
return determineReadPartitionForRequest(theRequest, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestPartitionId determineGenericPartitionForRequest(RequestDetails theRequestDetails);
|
RequestPartitionId determineGenericPartitionForRequest(RequestDetails theRequestDetails);
|
||||||
|
@ -54,18 +53,16 @@ public interface IRequestPartitionHelperSvc {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
default RequestPartitionId determineReadPartitionForRequestForHistory(RequestDetails theRequest, String theResourceType, IIdType theIdType) {
|
default RequestPartitionId determineReadPartitionForRequestForHistory(RequestDetails theRequest, String theResourceType, IIdType theIdType) {
|
||||||
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(theResourceType, theIdType);
|
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(theResourceType, theIdType);
|
||||||
return determineReadPartitionForRequest(theRequest, theResourceType, details);
|
return determineReadPartitionForRequest(theRequest, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
default void validateHasPartitionPermissions(RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId){}
|
default void validateHasPartitionPermissions(RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource, @Nonnull String theResourceType);
|
RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource, @Nonnull String theResourceType);
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
PartitionablePartitionId toStoragePartition(@Nonnull RequestPartitionId theRequestPartitionId);
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Set<Integer> toReadPartitions(@Nonnull RequestPartitionId theRequestPartitionId);
|
Set<Integer> toReadPartitions(@Nonnull RequestPartitionId theRequestPartitionId);
|
||||||
|
|
|
@ -121,8 +121,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
||||||
private BaseRuntimeChildDefinition myRangeHighValueChild;
|
private BaseRuntimeChildDefinition myRangeHighValueChild;
|
||||||
private BaseRuntimeChildDefinition myAddressLineValueChild;
|
private BaseRuntimeChildDefinition myAddressLineValueChild;
|
||||||
private BaseRuntimeChildDefinition myAddressCityValueChild;
|
private BaseRuntimeChildDefinition myAddressCityValueChild;
|
||||||
private BaseRuntimeChildDefinition myAddressStateValueChild;
|
|
||||||
private BaseRuntimeChildDefinition myAddressDistrictValueChild;
|
private BaseRuntimeChildDefinition myAddressDistrictValueChild;
|
||||||
|
private BaseRuntimeChildDefinition myAddressStateValueChild;
|
||||||
private BaseRuntimeChildDefinition myAddressCountryValueChild;
|
private BaseRuntimeChildDefinition myAddressCountryValueChild;
|
||||||
private BaseRuntimeChildDefinition myAddressPostalCodeValueChild;
|
private BaseRuntimeChildDefinition myAddressPostalCodeValueChild;
|
||||||
private BaseRuntimeChildDefinition myCapabilityStatementRestSecurityServiceValueChild;
|
private BaseRuntimeChildDefinition myCapabilityStatementRestSecurityServiceValueChild;
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package ca.uhn.fhir.jpa.searchparam.extractor;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR Search Parameters
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class CrossPartitionReferenceDetails {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private final RequestPartitionId mySourceResourcePartitionId;
|
||||||
|
@Nonnull
|
||||||
|
private final PathAndRef myPathAndRef;
|
||||||
|
@Nonnull
|
||||||
|
private final RequestDetails myRequestDetails;
|
||||||
|
@Nonnull
|
||||||
|
private final TransactionDetails myTransactionDetails;
|
||||||
|
@Nonnull
|
||||||
|
private final String mySourceResourceName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public CrossPartitionReferenceDetails(@Nonnull RequestPartitionId theSourceResourcePartitionId, @Nonnull String theSourceResourceName, @Nonnull PathAndRef thePathAndRef, @Nonnull RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails) {
|
||||||
|
mySourceResourcePartitionId = theSourceResourcePartitionId;
|
||||||
|
mySourceResourceName = theSourceResourceName;
|
||||||
|
myPathAndRef = thePathAndRef;
|
||||||
|
myRequestDetails = theRequestDetails;
|
||||||
|
myTransactionDetails = theTransactionDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public String getSourceResourceName() {
|
||||||
|
return mySourceResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public RequestDetails getRequestDetails() {
|
||||||
|
return myRequestDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public TransactionDetails getTransactionDetails() {
|
||||||
|
return myTransactionDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public RequestPartitionId getSourceResourcePartitionId() {
|
||||||
|
return mySourceResourcePartitionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public PathAndRef getPathAndRef() {
|
||||||
|
return myPathAndRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -31,8 +31,8 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
|
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
||||||
import ca.uhn.fhir.jpa.model.entity.IResourceIndexComboSearchParameter;
|
import ca.uhn.fhir.jpa.model.entity.IResourceIndexComboSearchParameter;
|
||||||
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
||||||
|
@ -50,6 +50,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||||
|
@ -71,8 +72,8 @@ import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
@ -93,6 +94,8 @@ public class SearchParamExtractorService {
|
||||||
private PartitionSettings myPartitionSettings;
|
private PartitionSettings myPartitionSettings;
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private IResourceLinkResolver myResourceLinkResolver;
|
private IResourceLinkResolver myResourceLinkResolver;
|
||||||
|
@Autowired
|
||||||
|
private IRequestPartitionHelperSvc myPartitionHelperSvc;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setSearchParamExtractor(ISearchParamExtractor theSearchParamExtractor) {
|
public void setSearchParamExtractor(ISearchParamExtractor theSearchParamExtractor) {
|
||||||
|
@ -567,19 +570,33 @@ public class SearchParamExtractorService {
|
||||||
* target any more times than we have to.
|
* target any more times than we have to.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
RequestPartitionId targetRequestPartitionId = theRequestPartitionId;
|
IResourceLookup<JpaPid> targetResource;
|
||||||
if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.getAllowReferencesAcrossPartitions() == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) {
|
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||||
targetRequestPartitionId = RequestPartitionId.allPartitions();
|
if (myPartitionSettings.getAllowReferencesAcrossPartitions() == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) {
|
||||||
|
|
||||||
|
// Interceptor: Pointcut.JPA_CROSS_PARTITION_REFERENCE_DETECTED
|
||||||
|
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.JPA_RESOLVE_CROSS_PARTITION_REFERENCE, myInterceptorBroadcaster, theRequest)) {
|
||||||
|
CrossPartitionReferenceDetails referenceDetails = new CrossPartitionReferenceDetails(theRequestPartitionId, theSourceResourceName, thePathAndRef, theRequest, theTransactionDetails);
|
||||||
|
HookParams params = new HookParams(referenceDetails);
|
||||||
|
targetResource = (IResourceLookup<JpaPid>) CompositeInterceptorBroadcaster.doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.JPA_RESOLVE_CROSS_PARTITION_REFERENCE, params);
|
||||||
|
} else {
|
||||||
|
targetResource = myResourceLinkResolver.findTargetResource(RequestPartitionId.allPartitions(), theSourceResourceName, thePathAndRef, theRequest, theTransactionDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
targetResource = myResourceLinkResolver.findTargetResource(theRequestPartitionId, theSourceResourceName, thePathAndRef, theRequest, theTransactionDetails);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetResource = myResourceLinkResolver.findTargetResource(theRequestPartitionId, theSourceResourceName, thePathAndRef, theRequest, theTransactionDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
IResourceLookup targetResource = myResourceLinkResolver.findTargetResource(targetRequestPartitionId, theSourceResourceName, thePathAndRef, theRequest, theTransactionDetails);
|
|
||||||
|
|
||||||
if (targetResource == null) {
|
if (targetResource == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String targetResourceType = targetResource.getResourceType();
|
String targetResourceType = targetResource.getResourceType();
|
||||||
Long targetResourcePid = ((JpaPid) targetResource.getPersistentId()).getId();
|
Long targetResourcePid = targetResource.getPersistentId().getId();
|
||||||
String targetResourceIdPart = theNextId.getIdPart();
|
String targetResourceIdPart = theNextId.getIdPart();
|
||||||
Long targetVersion = theNextId.getVersionIdPartAsLong();
|
Long targetVersion = theNextId.getVersionIdPartAsLong();
|
||||||
return ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime, targetVersion);
|
return ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime, targetVersion);
|
||||||
|
@ -671,6 +688,7 @@ public class SearchParamExtractorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If extraction generated any warnings, broadcast an error
|
// If extraction generated any warnings, broadcast an error
|
||||||
|
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_WARNING, theInterceptorBroadcaster, theRequestDetails)) {
|
||||||
for (String next : theSearchParamSet.getWarnings()) {
|
for (String next : theSearchParamSet.getWarnings()) {
|
||||||
StorageProcessingMessage messageHolder = new StorageProcessingMessage();
|
StorageProcessingMessage messageHolder = new StorageProcessingMessage();
|
||||||
messageHolder.setMessage(next);
|
messageHolder.setMessage(next);
|
||||||
|
@ -682,4 +700,5 @@ public class SearchParamExtractorService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,11 @@ public class SearchParamExtractorServiceTest {
|
||||||
searchParamSet.addWarning("help i'm a bug");
|
searchParamSet.addWarning("help i'm a bug");
|
||||||
searchParamSet.addWarning("Spiff");
|
searchParamSet.addWarning("Spiff");
|
||||||
|
|
||||||
|
when(myJpaInterceptorBroadcaster.hasHooks(any())).thenReturn(true);
|
||||||
when(myJpaInterceptorBroadcaster.callHooks(any(), any())).thenReturn(true);
|
when(myJpaInterceptorBroadcaster.callHooks(any(), any())).thenReturn(true);
|
||||||
|
|
||||||
SearchParamExtractorService.handleWarnings(new ServletRequestDetails(myRequestInterceptorBroadcaster), myJpaInterceptorBroadcaster, searchParamSet);
|
ServletRequestDetails requestDetails = new ServletRequestDetails(myRequestInterceptorBroadcaster);
|
||||||
|
SearchParamExtractorService.handleWarnings(requestDetails, myJpaInterceptorBroadcaster, searchParamSet);
|
||||||
|
|
||||||
verify(myJpaInterceptorBroadcaster, times(2)).callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), any());
|
verify(myJpaInterceptorBroadcaster, times(2)).callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), any());
|
||||||
verify(myRequestInterceptorBroadcaster, times(2)).callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), any());
|
verify(myRequestInterceptorBroadcaster, times(2)).callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), any());
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.api.svc.ISearchSvc;
|
import ca.uhn.fhir.jpa.api.svc.ISearchSvc;
|
||||||
|
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.model.sched.HapiJob;
|
import ca.uhn.fhir.jpa.model.sched.HapiJob;
|
||||||
import ca.uhn.fhir.jpa.model.sched.IHasScheduledJobs;
|
import ca.uhn.fhir.jpa.model.sched.IHasScheduledJobs;
|
||||||
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
||||||
|
@ -103,6 +104,8 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
|
||||||
private MatchUrlService myMatchUrlService;
|
private MatchUrlService myMatchUrlService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IResourceModifiedConsumer myResourceModifiedConsumer;
|
private IResourceModifiedConsumer myResourceModifiedConsumer;
|
||||||
|
@Autowired
|
||||||
|
private HapiTransactionService myTransactionService;
|
||||||
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
|
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
|
||||||
private ExecutorService myExecutorService;
|
private ExecutorService myExecutorService;
|
||||||
|
|
||||||
|
@ -150,8 +153,8 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
|
||||||
|
|
||||||
SubscriptionTriggeringJobDetails jobDetails = new SubscriptionTriggeringJobDetails();
|
SubscriptionTriggeringJobDetails jobDetails = new SubscriptionTriggeringJobDetails();
|
||||||
jobDetails.setJobId(UUID.randomUUID().toString());
|
jobDetails.setJobId(UUID.randomUUID().toString());
|
||||||
jobDetails.setRemainingResourceIds(resourceIds.stream().map(t -> t.getValue()).collect(Collectors.toList()));
|
jobDetails.setRemainingResourceIds(resourceIds.stream().map(IPrimitiveType::getValue).collect(Collectors.toList()));
|
||||||
jobDetails.setRemainingSearchUrls(searchUrls.stream().map(t -> t.getValue()).collect(Collectors.toList()));
|
jobDetails.setRemainingSearchUrls(searchUrls.stream().map(IPrimitiveType::getValue).collect(Collectors.toList()));
|
||||||
if (theSubscriptionId != null) {
|
if (theSubscriptionId != null) {
|
||||||
jobDetails.setSubscriptionId(theSubscriptionId.getIdPart());
|
jobDetails.setSubscriptionId(theSubscriptionId.getIdPart());
|
||||||
}
|
}
|
||||||
|
@ -329,12 +332,17 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
|
||||||
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(theJobDetails.getCurrentSearchResourceType());
|
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(theJobDetails.getCurrentSearchResourceType());
|
||||||
|
|
||||||
int maxQuerySize = myMaxSubmitPerPass - totalSubmitted;
|
int maxQuerySize = myMaxSubmitPerPass - totalSubmitted;
|
||||||
int toIndex = fromIndex + maxQuerySize;
|
int toIndex;
|
||||||
if (theJobDetails.getCurrentSearchCount() != null) {
|
if (theJobDetails.getCurrentSearchCount() != null) {
|
||||||
toIndex = Math.min(toIndex, theJobDetails.getCurrentSearchCount());
|
toIndex = Math.min(fromIndex + maxQuerySize, theJobDetails.getCurrentSearchCount());
|
||||||
|
} else {
|
||||||
|
toIndex = fromIndex + maxQuerySize;
|
||||||
}
|
}
|
||||||
|
|
||||||
ourLog.info("Triggering job[{}] search {} requesting resources {} - {}", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
|
ourLog.info("Triggering job[{}] search {} requesting resources {} - {}", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
|
||||||
List<IResourcePersistentId> resourceIds = mySearchCoordinatorSvc.getResources(theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex, null);
|
List<IResourcePersistentId<?>> resourceIds;
|
||||||
|
RequestPartitionId requestPartitionId = RequestPartitionId.allPartitions();
|
||||||
|
resourceIds = mySearchCoordinatorSvc.getResources(theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex, null, requestPartitionId);
|
||||||
|
|
||||||
ourLog.info("Triggering job[{}] delivering {} resources", theJobDetails.getJobId(), resourceIds.size());
|
ourLog.info("Triggering job[{}] delivering {} resources", theJobDetails.getJobId(), resourceIds.size());
|
||||||
int highestIndexSubmitted = theJobDetails.getCurrentSearchLastUploadedIndex();
|
int highestIndexSubmitted = theJobDetails.getCurrentSearchLastUploadedIndex();
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.cache.IResourceVersionSvc;
|
import ca.uhn.fhir.jpa.cache.IResourceVersionSvc;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig;
|
import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig;
|
||||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
||||||
import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory;
|
import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory;
|
||||||
|
@ -76,6 +77,11 @@ public class DaoSubscriptionMatcherTest {
|
||||||
return mock(IResourceVersionSvc.class, RETURNS_DEEP_STUBS);
|
return mock(IResourceVersionSvc.class, RETURNS_DEEP_STUBS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IRequestPartitionHelperSvc requestPartitionHelperSvc() {
|
||||||
|
return mock(IRequestPartitionHelperSvc.class);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.subscription.module;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig;
|
import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig;
|
||||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
|
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
|
||||||
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
|
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
|
||||||
|
@ -25,6 +26,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@ContextConfiguration(classes = {
|
@ContextConfiguration(classes = {
|
||||||
SearchParamConfig.class,
|
SearchParamConfig.class,
|
||||||
|
@ -84,6 +87,11 @@ public abstract class BaseSubscriptionTest {
|
||||||
return new InterceptorService();
|
return new InterceptorService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IRequestPartitionHelperSvc requestPartitionHelperSvc() {
|
||||||
|
return mock(IRequestPartitionHelperSvc.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
// Default implementation returns the name unchanged
|
// Default implementation returns the name unchanged
|
||||||
public IChannelNamer channelNamer() {
|
public IChannelNamer channelNamer() {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionCriteriaParser;
|
import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionCriteriaParser;
|
||||||
import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber;
|
import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber;
|
||||||
|
@ -20,14 +21,13 @@ import com.google.common.collect.Lists;
|
||||||
import org.hl7.fhir.dstu3.model.BooleanType;
|
import org.hl7.fhir.dstu3.model.BooleanType;
|
||||||
import org.hl7.fhir.dstu3.model.Observation;
|
import org.hl7.fhir.dstu3.model.Observation;
|
||||||
import org.hl7.fhir.dstu3.model.Subscription;
|
import org.hl7.fhir.dstu3.model.Subscription;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -46,7 +46,6 @@ import static org.mockito.Mockito.when;
|
||||||
* Tests copied from jpa.subscription.resthook.RestHookTestDstu3Test
|
* Tests copied from jpa.subscription.resthook.RestHookTestDstu3Test
|
||||||
*/
|
*/
|
||||||
public class SubscriptionMatchingSubscriberTest extends BaseBlockingQueueSubscribableChannelDstu3Test {
|
public class SubscriptionMatchingSubscriberTest extends BaseBlockingQueueSubscribableChannelDstu3Test {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionMatchingSubscriberTest.class);
|
|
||||||
private final IFhirResourceDao<Subscription> myMockSubscriptionDao = Mockito.mock(IFhirResourceDao.class);
|
private final IFhirResourceDao<Subscription> myMockSubscriptionDao = Mockito.mock(IFhirResourceDao.class);
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -55,6 +54,11 @@ public class SubscriptionMatchingSubscriberTest extends BaseBlockingQueueSubscri
|
||||||
myDaoRegistry.register(myMockSubscriptionDao);
|
myDaoRegistry.register(myMockSubscriptionDao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void afterEach() {
|
||||||
|
myStorageSettings.setCrossPartitionSubscriptionEnabled(new JpaStorageSettings().isCrossPartitionSubscriptionEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
|
public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
|
||||||
String payload = "application/fhir+json";
|
String payload = "application/fhir+json";
|
||||||
|
@ -392,6 +396,13 @@ public class SubscriptionMatchingSubscriberTest extends BaseBlockingQueueSubscri
|
||||||
ourObservationListener.awaitExpected();
|
ourObservationListener.awaitExpected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mockSubscriptionRead(RequestPartitionId theRequestPartitionId, Subscription subscription) {
|
||||||
|
Subscription modifiedSubscription = subscription.copy();
|
||||||
|
// the original partition info was the request info, but we need the actual storage partition.
|
||||||
|
modifiedSubscription.setUserData(Constants.RESOURCE_PARTITION_ID, theRequestPartitionId);
|
||||||
|
when(myMockSubscriptionDao.read(eq(subscription.getIdElement()), any())).thenReturn(modifiedSubscription);
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
public class TestDeleteMessages {
|
public class TestDeleteMessages {
|
||||||
private final SubscriptionMatchingSubscriber subscriber = new SubscriptionMatchingSubscriber();
|
private final SubscriptionMatchingSubscriber subscriber = new SubscriptionMatchingSubscriber();
|
||||||
|
@ -496,12 +507,4 @@ public class SubscriptionMatchingSubscriberTest extends BaseBlockingQueueSubscri
|
||||||
verify(message, atLeastOnce()).getPayloadId(null);
|
verify(message, atLeastOnce()).getPayloadId(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void mockSubscriptionRead(RequestPartitionId theRequestPartitionId, Subscription subscription) {
|
|
||||||
Subscription modifiedSubscription = subscription.copy();
|
|
||||||
// the original partition info was the request info, but we need the actual storage partition.
|
|
||||||
modifiedSubscription.setUserData(Constants.RESOURCE_PARTITION_ID, theRequestPartitionId);
|
|
||||||
when(myMockSubscriptionDao.read(eq(subscription.getIdElement()), any())).thenReturn(modifiedSubscription);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,10 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||||
public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
|
public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2ValidateTest.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2ValidateTest.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void before() {
|
public void before() throws Exception {
|
||||||
|
super.before();
|
||||||
myStorageSettings.setAllowExternalReferences(true);
|
myStorageSettings.setAllowExternalReferences(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2792,7 +2792,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
* Upload structurredef
|
* Upload structurredef
|
||||||
*/
|
*/
|
||||||
|
|
||||||
String contents = IOUtils.toString(getClass().getResourceAsStream("/allergyintolerance-sd-david.json"), "UTF-8");
|
String contents = ClasspathUtil.loadResource("/allergyintolerance-sd-david.json");
|
||||||
HttpEntityEnclosingRequestBase post = new HttpPut(myServerBase + "/StructureDefinition/ohAllergyIntolerance");
|
HttpEntityEnclosingRequestBase post = new HttpPut(myServerBase + "/StructureDefinition/ohAllergyIntolerance");
|
||||||
post.setEntity(new StringEntity(contents, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
|
post.setEntity(new StringEntity(contents, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
|
||||||
CloseableHttpResponse response = ourHttpClient.execute(post);
|
CloseableHttpResponse response = ourHttpClient.execute(post);
|
||||||
|
@ -2809,7 +2809,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
* Validate
|
* Validate
|
||||||
*/
|
*/
|
||||||
|
|
||||||
contents = IOUtils.toString(getClass().getResourceAsStream("/allergyintolerance-david.json"), "UTF-8");
|
contents = ClasspathUtil.loadResource("/allergyintolerance-david.json");
|
||||||
|
|
||||||
post = new HttpPost(myServerBase + "/AllergyIntolerance/$validate?_pretty=true");
|
post = new HttpPost(myServerBase + "/AllergyIntolerance/$validate?_pretty=true");
|
||||||
post.setEntity(new StringEntity(contents, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
|
post.setEntity(new StringEntity(contents, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
|
||||||
|
|
|
@ -82,9 +82,10 @@ import static org.mockito.Mockito.when;
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
|
public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(SearchCoordinatorSvcImplTest.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(SearchCoordinatorSvcImplTest.class);
|
||||||
|
@Spy
|
||||||
@Mock private SearchStrategyFactory mySearchStrategyFactory;
|
protected FhirContext myContext = FhirContext.forDstu2Cached();
|
||||||
|
@Mock
|
||||||
|
private SearchStrategyFactory mySearchStrategyFactory;
|
||||||
@Mock
|
@Mock
|
||||||
private ISearchCacheSvc mySearchCacheSvc;
|
private ISearchCacheSvc mySearchCacheSvc;
|
||||||
@Mock
|
@Mock
|
||||||
|
@ -100,9 +101,6 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
private IRequestPartitionHelperSvc myPartitionHelperSvc;
|
private IRequestPartitionHelperSvc myPartitionHelperSvc;
|
||||||
@Mock
|
@Mock
|
||||||
private ISynchronousSearchSvc mySynchronousSearchSvc;
|
private ISynchronousSearchSvc mySynchronousSearchSvc;
|
||||||
@Spy
|
|
||||||
protected FhirContext myContext = FhirContext.forDstu2Cached();
|
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
private ExceptionService myExceptionSvc = new ExceptionService(myContext);
|
private ExceptionService myExceptionSvc = new ExceptionService(myContext);
|
||||||
|
|
||||||
|
@ -118,6 +116,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void before() {
|
public void before() {
|
||||||
HapiSystemProperties.enableUnitTestCaptureStack();
|
HapiSystemProperties.enableUnitTestCaptureStack();
|
||||||
|
HapiSystemProperties.enableUnitTestMode();
|
||||||
|
|
||||||
myCurrentSearch = null;
|
myCurrentSearch = null;
|
||||||
|
|
||||||
|
@ -178,7 +177,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
assertEquals(allResults.size(), oldResults.size());
|
assertEquals(allResults.size(), oldResults.size());
|
||||||
allResults.addAll(newResults);
|
allResults.addAll(newResults);
|
||||||
return null;
|
return null;
|
||||||
}).when(mySearchResultCacheSvc).storeResults(any(), anyList(), anyList());
|
}).when(mySearchResultCacheSvc).storeResults(any(), anyList(), anyList(), any(), any());
|
||||||
|
|
||||||
SearchParameterMap params = new SearchParameterMap();
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
params.add("name", new StringParam("ANAME"));
|
params.add("name", new StringParam("ANAME"));
|
||||||
|
@ -233,7 +232,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mySvc.getResources("1234-5678", 0, 100, null);
|
mySvc.getResources("1234-5678", 0, 100, null, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (ResourceGoneException e) {
|
} catch (ResourceGoneException e) {
|
||||||
assertEquals("Search ID \"1234-5678\" does not exist and may have expired", e.getMessage());
|
assertEquals("Search ID \"1234-5678\" does not exist and may have expired", e.getMessage());
|
||||||
|
@ -255,7 +254,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mySvc.getResources("1234-5678", 0, 100, null);
|
mySvc.getResources("1234-5678", 0, 100, null, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (InternalErrorException e) {
|
} catch (InternalErrorException e) {
|
||||||
assertThat(e.getMessage(), containsString("Request timed out"));
|
assertThat(e.getMessage(), containsString("Request timed out"));
|
||||||
|
@ -295,15 +294,16 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initAsyncSearches() {
|
private void initAsyncSearches() {
|
||||||
when(myPersistedJpaBundleProviderFactory.newInstanceFirstPage(nullable(RequestDetails.class), nullable(Search.class), nullable(SearchTask.class), nullable(ISearchBuilder.class))).thenAnswer(t->{
|
when(myPersistedJpaBundleProviderFactory.newInstanceFirstPage(nullable(RequestDetails.class), nullable(Search.class), nullable(SearchTask.class), nullable(ISearchBuilder.class), nullable(RequestPartitionId.class))).thenAnswer(t -> {
|
||||||
RequestDetails requestDetails = t.getArgument(0, RequestDetails.class);
|
RequestDetails requestDetails = t.getArgument(0, RequestDetails.class);
|
||||||
Search search = t.getArgument(1, Search.class);
|
Search search = t.getArgument(1, Search.class);
|
||||||
SearchTask searchTask = t.getArgument(2, SearchTask.class);
|
SearchTask searchTask = t.getArgument(2, SearchTask.class);
|
||||||
ISearchBuilder<JpaPid> searchBuilder = t.getArgument(3, ISearchBuilder.class);
|
ISearchBuilder<JpaPid> searchBuilder = t.getArgument(3, ISearchBuilder.class);
|
||||||
PersistedJpaSearchFirstPageBundleProvider retVal = new PersistedJpaSearchFirstPageBundleProvider(search, searchTask, searchBuilder, requestDetails);
|
PersistedJpaSearchFirstPageBundleProvider retVal = new PersistedJpaSearchFirstPageBundleProvider(search, searchTask, searchBuilder, requestDetails, null);
|
||||||
retVal.setStorageSettingsForUnitTest(new JpaStorageSettings());
|
retVal.setStorageSettingsForUnitTest(new JpaStorageSettings());
|
||||||
retVal.setTxServiceForUnitTest(myTransactionService);
|
retVal.setTxServiceForUnitTest(myTransactionService);
|
||||||
retVal.setSearchCoordinatorSvcForUnitTest(mySvc);
|
retVal.setSearchCoordinatorSvcForUnitTest(mySvc);
|
||||||
|
retVal.setRequestPartitionHelperSvcForUnitTest(myPartitionHelperSvc);
|
||||||
retVal.setContext(myContext);
|
retVal.setContext(myContext);
|
||||||
return retVal;
|
return retVal;
|
||||||
});
|
});
|
||||||
|
@ -333,7 +333,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
try {
|
try {
|
||||||
assertNotNull(searchId);
|
assertNotNull(searchId);
|
||||||
ourLog.info("About to pull the first resource");
|
ourLog.info("About to pull the first resource");
|
||||||
List<JpaPid> resources = mySvc.getResources(searchId, 0, 1, null);
|
List<JpaPid> resources = mySvc.getResources(searchId, 0, 1, null, null);
|
||||||
ourLog.info("Done pulling the first resource");
|
ourLog.info("Done pulling the first resource");
|
||||||
assertEquals(1, resources.size());
|
assertEquals(1, resources.size());
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -349,7 +349,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
ourLog.info("Done cancelling all searches");
|
ourLog.info("Done cancelling all searches");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mySvc.getResources(searchId, 0, 1, null);
|
mySvc.getResources(searchId, 0, 1, null, null);
|
||||||
} catch (ResourceGoneException e) {
|
} catch (ResourceGoneException e) {
|
||||||
// good
|
// good
|
||||||
}
|
}
|
||||||
|
@ -448,6 +448,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
search.setSearchType(SearchTypeEnum.SEARCH);
|
search.setSearchType(SearchTypeEnum.SEARCH);
|
||||||
search.setResourceType("Patient");
|
search.setResourceType("Patient");
|
||||||
search.setStatus(SearchStatusEnum.LOADING);
|
search.setStatus(SearchStatusEnum.LOADING);
|
||||||
|
search.setSearchParameterMap(new SearchParameterMap());
|
||||||
|
|
||||||
when(mySearchCacheSvc.fetchByUuid(eq(uuid))).thenReturn(Optional.of(search));
|
when(mySearchCacheSvc.fetchByUuid(eq(uuid))).thenReturn(Optional.of(search));
|
||||||
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
|
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
|
||||||
|
@ -462,7 +463,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
when(mySearchResultCacheSvc.fetchResultPids(any(Search.class), anyInt(), anyInt())).thenAnswer(theInvocation -> {
|
when(mySearchResultCacheSvc.fetchResultPids(any(Search.class), anyInt(), anyInt(), any(), any())).thenAnswer(theInvocation -> {
|
||||||
ArrayList<IResourcePersistentId> results = new ArrayList<>();
|
ArrayList<IResourcePersistentId> results = new ArrayList<>();
|
||||||
for (long i = theInvocation.getArgument(1, Integer.class); i < theInvocation.getArgument(2, Integer.class); i++) {
|
for (long i = theInvocation.getArgument(1, Integer.class); i < theInvocation.getArgument(2, Integer.class); i++) {
|
||||||
Long nextPid = i + 10L;
|
Long nextPid = i + 10L;
|
||||||
|
@ -492,6 +493,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
provider.setSearchBuilderFactoryForUnitTest(mySearchBuilderFactory);
|
provider.setSearchBuilderFactoryForUnitTest(mySearchBuilderFactory);
|
||||||
provider.setSearchCoordinatorSvcForUnitTest(mySvc);
|
provider.setSearchCoordinatorSvcForUnitTest(mySvc);
|
||||||
provider.setStorageSettingsForUnitTest(new JpaStorageSettings());
|
provider.setStorageSettingsForUnitTest(new JpaStorageSettings());
|
||||||
|
provider.setRequestPartitionId(RequestPartitionId.defaultPartition());
|
||||||
resources = provider.getResources(20, 40);
|
resources = provider.getResources(20, 40);
|
||||||
assertEquals(20, resources.size());
|
assertEquals(20, resources.size());
|
||||||
assertEquals("30", resources.get(0).getIdElement().getValueAsString());
|
assertEquals("30", resources.get(0).getIdElement().getValueAsString());
|
||||||
|
@ -511,6 +513,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
provider.setDaoRegistryForUnitTest(myDaoRegistry);
|
provider.setDaoRegistryForUnitTest(myDaoRegistry);
|
||||||
provider.setSearchCoordinatorSvcForUnitTest(mySvc);
|
provider.setSearchCoordinatorSvcForUnitTest(mySvc);
|
||||||
provider.setStorageSettingsForUnitTest(new JpaStorageSettings());
|
provider.setStorageSettingsForUnitTest(new JpaStorageSettings());
|
||||||
|
provider.setRequestPartitionId(RequestPartitionId.defaultPartition());
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,10 +569,10 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
search.setTotalCount(100);
|
search.setTotalCount(100);
|
||||||
when(mySearchCacheSvc.fetchByUuid(eq("0000-1111"))).thenReturn(Optional.of(search));
|
when(mySearchCacheSvc.fetchByUuid(eq("0000-1111"))).thenReturn(Optional.of(search));
|
||||||
|
|
||||||
when(mySearchResultCacheSvc.fetchResultPids(any(), anyInt(), anyInt())).thenReturn(null);
|
when(mySearchResultCacheSvc.fetchResultPids(any(), anyInt(), anyInt(), any(), any())).thenReturn(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mySvc.getResources("0000-1111", 0, 10, null);
|
mySvc.getResources("0000-1111", 0, 10, null, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (ResourceGoneException e) {
|
} catch (ResourceGoneException e) {
|
||||||
assertEquals("Search ID \"0000-1111\" does not exist and may have expired", e.getMessage());
|
assertEquals("Search ID \"0000-1111\" does not exist and may have expired", e.getMessage());
|
||||||
|
@ -599,10 +602,10 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
});
|
});
|
||||||
mockSearchTask();
|
mockSearchTask();
|
||||||
|
|
||||||
when(mySearchResultCacheSvc.fetchAllResultPids(any())).thenReturn(null);
|
when(mySearchResultCacheSvc.fetchAllResultPids(any(), any(), any())).thenReturn(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mySvc.getResources("0000-1111", 0, 10, null);
|
mySvc.getResources("0000-1111", 0, 10, null, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (ResourceGoneException e) {
|
} catch (ResourceGoneException e) {
|
||||||
assertEquals("Search ID \"0000-1111\" does not exist and may have expired", e.getMessage());
|
assertEquals("Search ID \"0000-1111\" does not exist and may have expired", e.getMessage());
|
||||||
|
@ -610,10 +613,53 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mockSearchTask() {
|
||||||
|
IPagingProvider pagingProvider = mock(IPagingProvider.class);
|
||||||
|
lenient().when(pagingProvider.getMaximumPageSize())
|
||||||
|
.thenReturn(500);
|
||||||
|
when(myBeanFactory.getBean(anyString(), any(SearchTaskParameters.class)))
|
||||||
|
.thenAnswer(invocation -> {
|
||||||
|
String type = invocation.getArgument(0);
|
||||||
|
switch (type) {
|
||||||
|
case SearchConfig.SEARCH_TASK -> {
|
||||||
|
return new SearchTask(
|
||||||
|
invocation.getArgument(1),
|
||||||
|
myTransactionService,
|
||||||
|
ourCtx,
|
||||||
|
myInterceptorBroadcaster,
|
||||||
|
mySearchBuilderFactory,
|
||||||
|
mySearchResultCacheSvc,
|
||||||
|
myStorageSettings,
|
||||||
|
mySearchCacheSvc,
|
||||||
|
pagingProvider
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case SearchConfig.CONTINUE_TASK -> {
|
||||||
|
return new SearchContinuationTask(
|
||||||
|
invocation.getArgument(1),
|
||||||
|
myTransactionService,
|
||||||
|
ourCtx,
|
||||||
|
myInterceptorBroadcaster,
|
||||||
|
mySearchBuilderFactory,
|
||||||
|
mySearchResultCacheSvc,
|
||||||
|
myStorageSettings,
|
||||||
|
mySearchCacheSvc,
|
||||||
|
pagingProvider,
|
||||||
|
myExceptionSvc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
fail("Invalid bean type: " + type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static class FailAfterNIterator extends BaseIterator<JpaPid> implements IResultIterator<JpaPid> {
|
public static class FailAfterNIterator extends BaseIterator<JpaPid> implements IResultIterator<JpaPid> {
|
||||||
|
|
||||||
private int myCount;
|
|
||||||
private final IResultIterator<JpaPid> myWrap;
|
private final IResultIterator<JpaPid> myWrap;
|
||||||
|
private int myCount;
|
||||||
|
|
||||||
FailAfterNIterator(IResultIterator theWrap, int theCount) {
|
FailAfterNIterator(IResultIterator theWrap, int theCount) {
|
||||||
myWrap = theWrap;
|
myWrap = theWrap;
|
||||||
|
@ -738,45 +784,4 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc{
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void mockSearchTask() {
|
|
||||||
IPagingProvider pagingProvider = mock(IPagingProvider.class);
|
|
||||||
lenient().when(pagingProvider.getMaximumPageSize())
|
|
||||||
.thenReturn(500);
|
|
||||||
when(myBeanFactory.getBean(anyString(), any(SearchTaskParameters.class)))
|
|
||||||
.thenAnswer(invocation -> {
|
|
||||||
String type = invocation.getArgument(0);
|
|
||||||
switch (type) {
|
|
||||||
case SearchConfig.SEARCH_TASK:
|
|
||||||
return new SearchTask(
|
|
||||||
invocation.getArgument(1),
|
|
||||||
myTransactionService,
|
|
||||||
ourCtx,
|
|
||||||
myInterceptorBroadcaster,
|
|
||||||
mySearchBuilderFactory,
|
|
||||||
mySearchResultCacheSvc,
|
|
||||||
myStorageSettings,
|
|
||||||
mySearchCacheSvc,
|
|
||||||
pagingProvider
|
|
||||||
);
|
|
||||||
case SearchConfig.CONTINUE_TASK:
|
|
||||||
return new SearchContinuationTask(
|
|
||||||
invocation.getArgument(1),
|
|
||||||
myTransactionService,
|
|
||||||
ourCtx,
|
|
||||||
myInterceptorBroadcaster,
|
|
||||||
mySearchBuilderFactory,
|
|
||||||
mySearchResultCacheSvc,
|
|
||||||
myStorageSettings,
|
|
||||||
mySearchCacheSvc,
|
|
||||||
pagingProvider,
|
|
||||||
myExceptionSvc
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
fail("Invalid bean type: " + type);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -2553,17 +2553,16 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
p.addName().setFamily("testMetaAddInvalid");
|
p.addName().setFamily("testMetaAddInvalid");
|
||||||
IIdType id = myClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
|
IIdType id = myClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
//@formatter:off
|
String input = """
|
||||||
String input = "<Parameters>\n" +
|
<Parameters>
|
||||||
" <meta>\n" +
|
<meta>
|
||||||
" <tag>\n" +
|
<tag>
|
||||||
" <system value=\"http://example.org/codes/tags\"/>\n" +
|
<system value="http://example.org/codes/tags"/>
|
||||||
" <code value=\"record-lost\"/>\n" +
|
<code value="record-lost"/>
|
||||||
" <display value=\"Patient File Lost\"/>\n" +
|
<display value="Patient File Lost"/>
|
||||||
" </tag>\n" +
|
</tag>
|
||||||
" </meta>\n" +
|
</meta>
|
||||||
"</Parameters>";
|
</Parameters>""";
|
||||||
//@formatter:on
|
|
||||||
|
|
||||||
HttpPost post = new HttpPost(myServerBase + "/Patient/" + id.getIdPart() + "/$meta-add");
|
HttpPost post = new HttpPost(myServerBase + "/Patient/" + id.getIdPart() + "/$meta-add");
|
||||||
post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
|
|
@ -103,7 +103,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv
|
||||||
myExtensionalCsId_v1 = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
|
myExtensionalCsId_v1 = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
myCodeSystemDao.readEntity(myExtensionalCsId_v1, null).getId();
|
myCodeSystemDao.readEntity(myExtensionalCsId_v1, null);
|
||||||
|
|
||||||
theCodeSystem.setId("CodeSystem/cs2");
|
theCodeSystem.setId("CodeSystem/cs2");
|
||||||
theCodeSystem.setVersion("2");
|
theCodeSystem.setVersion("2");
|
||||||
|
@ -116,7 +116,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv
|
||||||
myExtensionalCsId_v2 = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
|
myExtensionalCsId_v2 = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
myCodeSystemDao.readEntity(myExtensionalCsId_v2, null).getId();
|
myCodeSystemDao.readEntity(myExtensionalCsId_v2, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,13 +126,13 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv
|
||||||
valueSet.setId("ValueSet/vs1");
|
valueSet.setId("ValueSet/vs1");
|
||||||
valueSet.getCompose().getInclude().get(0).setVersion("1");
|
valueSet.getCompose().getInclude().get(0).setVersion("1");
|
||||||
myExtensionalVsId_v1 = persistSingleValueSet(valueSet, HttpVerb.POST);
|
myExtensionalVsId_v1 = persistSingleValueSet(valueSet, HttpVerb.POST);
|
||||||
myExtensionalVsIdOnResourceTable_v1 = myValueSetDao.readEntity(myExtensionalVsId_v1, null).getId();
|
myExtensionalVsIdOnResourceTable_v1 = myValueSetDao.readEntity(myExtensionalVsId_v1, null).getIdDt().getIdPartAsLong();
|
||||||
|
|
||||||
valueSet.setVersion("2");
|
valueSet.setVersion("2");
|
||||||
valueSet.setId("ValueSet/vs2");
|
valueSet.setId("ValueSet/vs2");
|
||||||
valueSet.getCompose().getInclude().get(0).setVersion("2");
|
valueSet.getCompose().getInclude().get(0).setVersion("2");
|
||||||
myExtensionalVsId_v2 = persistSingleValueSet(valueSet, HttpVerb.POST);
|
myExtensionalVsId_v2 = persistSingleValueSet(valueSet, HttpVerb.POST);
|
||||||
myExtensionalVsIdOnResourceTable_v2 = myValueSetDao.readEntity(myExtensionalVsId_v2, null).getId();
|
myExtensionalVsIdOnResourceTable_v2 = myValueSetDao.readEntity(myExtensionalVsId_v2, null).getIdDt().getIdPartAsLong();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- define the root first, so the rest can inherit our logger -->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<logger name="ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber" level="info"/>
|
||||||
|
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" level="info"/>
|
||||||
|
<logger name="ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2" level="info"/>
|
||||||
|
<logger name="org.eclipse.jetty.websocket" level="info"/>
|
||||||
|
<logger name="org.hibernate.event.internal.DefaultPersistEventListener" level="info"/>
|
||||||
|
<logger name="org.eclipse" level="error"/>
|
||||||
|
<logger name="ca.uhn.fhir.rest.client" level="info"/>
|
||||||
|
<logger name="ca.uhn.fhir.jpa.dao" level="info"/>
|
||||||
|
|
||||||
|
<!-- set to debug to enable term expansion logs -->
|
||||||
|
|
||||||
|
<logger name="ca.uhn.fhir.jpa.term" level="info"/>
|
||||||
|
<!-- Set to 'trace' to enable SQL logging -->
|
||||||
|
<logger name="org.hibernate.SQL" level="info"/>
|
||||||
|
<!-- Set to 'trace' to enable SQL Value logging -->
|
||||||
|
<logger name="org.hibernate.type" level="info"/>
|
||||||
|
|
||||||
|
<logger name="org.springframework.test.context.cache" level="info"/>
|
||||||
|
<logger name="ca.uhn.fhir.jpa.bulk" level="info"/>
|
||||||
|
|
||||||
|
<!-- more debugging -->
|
||||||
|
<!--
|
||||||
|
<logger name="org.elasticsearch.client" level="trace"/>
|
||||||
|
<logger name="org.hibernate.search.elasticsearch.request" level="TRACE"/>
|
||||||
|
<logger name="ca.uhn.fhir.jpa.model.search" level="debug"/>
|
||||||
|
<logger name="org.elasticsearch.client" level="trace"/>
|
||||||
|
<logger name="org.hibernate.search" level="debug"/>
|
||||||
|
<logger name="org.hibernate.search.query" level="TRACE"/>
|
||||||
|
<logger name="org.hibernate.search.elasticsearch.request" level="TRACE"/>
|
||||||
|
-->
|
||||||
|
<!-- See https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#backend-lucene-io-writer-infostream for lucene logging
|
||||||
|
<logger name="org.hibernate.search.backend.lucene.infostream" level="TRACE"/> -->
|
||||||
|
|
||||||
|
</configuration>
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>6.5.2-SNAPSHOT</version>
|
<version>6.5.3-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -88,10 +88,14 @@ public class Batch2CoordinatorIT extends BaseJpaR4Test {
|
||||||
return RunOutcome.SUCCESS;
|
return RunOutcome.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void before() {
|
public void before() throws Exception {
|
||||||
|
super.before();
|
||||||
|
|
||||||
myCompletionHandler = details -> {};
|
myCompletionHandler = details -> {};
|
||||||
myWorkChannel = (LinkedBlockingChannel) myChannelFactory.getOrCreateReceiver(CHANNEL_NAME, JobWorkNotificationJsonMessage.class, new ChannelConsumerSettings());
|
myWorkChannel = (LinkedBlockingChannel) myChannelFactory.getOrCreateReceiver(CHANNEL_NAME, JobWorkNotificationJsonMessage.class, new ChannelConsumerSettings());
|
||||||
|
myStorageSettings.setJobFastTrackingEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
|
|
|
@ -17,9 +17,9 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.client.apache.ResourceEntity;
|
import ca.uhn.fhir.rest.client.apache.ResourceEntity;
|
||||||
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
|
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
import ca.uhn.fhir.test.utilities.HttpClientExtension;
|
||||||
|
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||||
import ca.uhn.fhir.util.JsonUtil;
|
import ca.uhn.fhir.util.JsonUtil;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
@ -28,20 +28,14 @@ import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpDelete;
|
import org.apache.http.client.methods.HttpDelete;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
|
||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
import org.hl7.fhir.r4.model.InstantType;
|
import org.hl7.fhir.r4.model.InstantType;
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
@ -54,12 +48,12 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
@ -89,53 +83,19 @@ public class BulkDataExportProviderTest {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(BulkDataExportProviderTest.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(BulkDataExportProviderTest.class);
|
||||||
private static final String GROUP_ID = "Group/G2401";
|
private static final String GROUP_ID = "Group/G2401";
|
||||||
private static final String G_JOB_ID = "0000000-GGGGGG";
|
private static final String G_JOB_ID = "0000000-GGGGGG";
|
||||||
private Server myServer;
|
|
||||||
@Spy
|
@Spy
|
||||||
private final FhirContext myCtx = FhirContext.forR4Cached();
|
private final FhirContext myCtx = FhirContext.forR4Cached();
|
||||||
private int myPort;
|
@RegisterExtension
|
||||||
|
private final HttpClientExtension myClient = new HttpClientExtension();
|
||||||
@Mock
|
@Mock
|
||||||
private IBatch2JobRunner myJobRunner;
|
private IBatch2JobRunner myJobRunner;
|
||||||
|
|
||||||
private JpaStorageSettings myStorageSettings;
|
|
||||||
private DaoRegistry myDaoRegistry;
|
|
||||||
private CloseableHttpClient myClient;
|
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private BulkDataExportProvider myProvider;
|
private BulkDataExportProvider myProvider;
|
||||||
private RestfulServer servlet;
|
@RegisterExtension
|
||||||
|
private final RestfulServerExtension myServer = new RestfulServerExtension(myCtx)
|
||||||
static Stream<Arguments> paramsProvider(){
|
.withServer(s -> s.registerProvider(myProvider));
|
||||||
return Stream.of(
|
private JpaStorageSettings myStorageSettings;
|
||||||
Arguments.arguments(true),
|
private DaoRegistry myDaoRegistry;
|
||||||
Arguments.arguments(false)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void after() throws Exception {
|
|
||||||
JettyUtil.closeServer(myServer);
|
|
||||||
myClient.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void start() throws Exception {
|
|
||||||
myServer = new Server(0);
|
|
||||||
|
|
||||||
ServletHandler proxyHandler = new ServletHandler();
|
|
||||||
servlet = new RestfulServer(myCtx);
|
|
||||||
servlet.registerProvider(myProvider);
|
|
||||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
|
||||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
|
||||||
myServer.setHandler(proxyHandler);
|
|
||||||
JettyUtil.startServer(myServer);
|
|
||||||
myPort = JettyUtil.getPortForStartedServer(myServer);
|
|
||||||
|
|
||||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
|
||||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
|
||||||
builder.setConnectionManager(connectionManager);
|
|
||||||
myClient = builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void injectStorageSettings() {
|
public void injectStorageSettings() {
|
||||||
|
@ -147,9 +107,9 @@ public class BulkDataExportProviderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startWithFixedBaseUrl() {
|
public void startWithFixedBaseUrl() {
|
||||||
String baseUrl = "http://localhost:" + myPort + "/fixedvalue";
|
String baseUrl = myServer.getBaseUrl() + "/fixedvalue";
|
||||||
HardcodedServerAddressStrategy hardcodedServerAddressStrategy = new HardcodedServerAddressStrategy(baseUrl);
|
HardcodedServerAddressStrategy hardcodedServerAddressStrategy = new HardcodedServerAddressStrategy(baseUrl);
|
||||||
servlet.setServerAddressStrategy(hardcodedServerAddressStrategy);
|
myServer.withServer(s -> s.setServerAddressStrategy(hardcodedServerAddressStrategy));
|
||||||
}
|
}
|
||||||
|
|
||||||
private BulkExportParameters verifyJobStart() {
|
private BulkExportParameters verifyJobStart() {
|
||||||
|
@ -196,7 +156,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.debug(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
ourLog.debug(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
||||||
|
|
||||||
// test
|
// test
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
ourLog.info("Request: {}", post);
|
ourLog.info("Request: {}", post);
|
||||||
|
@ -205,7 +165,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BulkExportParameters params = verifyJobStart();
|
BulkExportParameters params = verifyJobStart();
|
||||||
|
@ -223,7 +183,7 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(createJobStartResponse());
|
.thenReturn(createJobStartResponse());
|
||||||
|
|
||||||
Parameters input = new Parameters();
|
Parameters input = new Parameters();
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
|
|
||||||
|
@ -243,7 +203,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
InstantType now = InstantType.now();
|
InstantType now = InstantType.now();
|
||||||
|
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient, Practitioner")
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient, Practitioner")
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_SINCE + "=" + UrlUtil.escapeUrlParam(now.getValueAsString())
|
+ "&" + JpaConstants.PARAM_EXPORT_SINCE + "=" + UrlUtil.escapeUrlParam(now.getValueAsString())
|
||||||
|
@ -257,7 +217,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BulkExportParameters params = verifyJobStart();
|
BulkExportParameters params = verifyJobStart();
|
||||||
|
@ -272,7 +232,7 @@ public class BulkDataExportProviderTest {
|
||||||
when(myJobRunner.startNewJob(any()))
|
when(myJobRunner.startNewJob(any()))
|
||||||
.thenReturn(createJobStartResponse());
|
.thenReturn(createJobStartResponse());
|
||||||
|
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient,EpisodeOfCare")
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient,EpisodeOfCare")
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE_FILTER + "=" + UrlUtil.escapeUrlParam("Patient?_id=P999999990")
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE_FILTER + "=" + UrlUtil.escapeUrlParam("Patient?_id=P999999990")
|
||||||
|
@ -286,7 +246,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BulkExportParameters params = verifyJobStart();
|
BulkExportParameters params = verifyJobStart();
|
||||||
|
@ -309,7 +269,7 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(info);
|
.thenReturn(info);
|
||||||
|
|
||||||
// test
|
// test
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
|
@ -338,7 +298,7 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(info);
|
.thenReturn(info);
|
||||||
|
|
||||||
// call
|
// call
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
|
@ -382,7 +342,7 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(info);
|
.thenReturn(info);
|
||||||
|
|
||||||
// call
|
// call
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
|
@ -398,11 +358,11 @@ public class BulkDataExportProviderTest {
|
||||||
BulkExportResponseJson responseJson = JsonUtil.deserialize(responseContent, BulkExportResponseJson.class);
|
BulkExportResponseJson responseJson = JsonUtil.deserialize(responseContent, BulkExportResponseJson.class);
|
||||||
assertEquals(3, responseJson.getOutput().size());
|
assertEquals(3, responseJson.getOutput().size());
|
||||||
assertEquals("Patient", responseJson.getOutput().get(0).getType());
|
assertEquals("Patient", responseJson.getOutput().get(0).getType());
|
||||||
assertEquals("http://localhost:" + myPort + "/Binary/111", responseJson.getOutput().get(0).getUrl());
|
assertEquals(myServer.getBaseUrl() + "/Binary/111", responseJson.getOutput().get(0).getUrl());
|
||||||
assertEquals("Patient", responseJson.getOutput().get(1).getType());
|
assertEquals("Patient", responseJson.getOutput().get(1).getType());
|
||||||
assertEquals("http://localhost:" + myPort + "/Binary/222", responseJson.getOutput().get(1).getUrl());
|
assertEquals(myServer.getBaseUrl() + "/Binary/222", responseJson.getOutput().get(1).getUrl());
|
||||||
assertEquals("Patient", responseJson.getOutput().get(2).getType());
|
assertEquals("Patient", responseJson.getOutput().get(2).getType());
|
||||||
assertEquals("http://localhost:" + myPort + "/Binary/333", responseJson.getOutput().get(2).getUrl());
|
assertEquals(myServer.getBaseUrl() + "/Binary/333", responseJson.getOutput().get(2).getUrl());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +387,7 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(info);
|
.thenReturn(info);
|
||||||
|
|
||||||
// test
|
// test
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
|
@ -453,7 +413,7 @@ public class BulkDataExportProviderTest {
|
||||||
when(myJobRunner.getJobInfo(anyString()))
|
when(myJobRunner.getJobInfo(anyString()))
|
||||||
.thenThrow(new ResourceNotFoundException("Unknown job: AAA"));
|
.thenThrow(new ResourceNotFoundException("Unknown job: AAA"));
|
||||||
|
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
|
@ -470,7 +430,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group export tests
|
* Group export tests
|
||||||
* See https://build.fhir.org/ig/HL7/us-bulk-data/
|
* See <a href="https://build.fhir.org/ig/HL7/us-bulk-data/">Bulk Data IG</a>
|
||||||
* <p>
|
* <p>
|
||||||
* GET [fhir base]/Group/[id]/$export
|
* GET [fhir base]/Group/[id]/$export
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -496,7 +456,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.debug(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
ourLog.debug(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
||||||
|
|
||||||
// call
|
// call
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + GROUP_ID + "/" + JpaConstants.OPERATION_EXPORT);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + GROUP_ID + "/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
ourLog.info("Request: {}", post);
|
ourLog.info("Request: {}", post);
|
||||||
|
@ -504,7 +464,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.info("Response: {}", response.toString());
|
ourLog.info("Response: {}", response.toString());
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + G_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + G_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
|
@ -525,7 +485,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
InstantType now = InstantType.now();
|
InstantType now = InstantType.now();
|
||||||
|
|
||||||
String url = "http://localhost:" + myPort + "/" + GROUP_ID + "/" + JpaConstants.OPERATION_EXPORT
|
String url = myServer.getBaseUrl() + "/" + GROUP_ID + "/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient, Practitioner")
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient, Practitioner")
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_SINCE + "=" + UrlUtil.escapeUrlParam(now.getValueAsString())
|
+ "&" + JpaConstants.PARAM_EXPORT_SINCE + "=" + UrlUtil.escapeUrlParam(now.getValueAsString())
|
||||||
|
@ -541,7 +501,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + G_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + G_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BulkExportParameters bp = verifyJobStart();
|
BulkExportParameters bp = verifyJobStart();
|
||||||
|
@ -559,7 +519,7 @@ public class BulkDataExportProviderTest {
|
||||||
InstantType now = InstantType.now();
|
InstantType now = InstantType.now();
|
||||||
|
|
||||||
// manual construct
|
// manual construct
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Immunization, Observation")
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Immunization, Observation")
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_SINCE + "=" + UrlUtil.escapeUrlParam(now.getValueAsString());
|
+ "&" + JpaConstants.PARAM_EXPORT_SINCE + "=" + UrlUtil.escapeUrlParam(now.getValueAsString());
|
||||||
|
@ -567,45 +527,45 @@ public class BulkDataExportProviderTest {
|
||||||
String immunizationTypeFilter1 = "Immunization?patient.identifier=SC378274-MRN|009999997,SC378274-MRN|009999998,SC378274-MRN|009999999&date=2020-01-02";
|
String immunizationTypeFilter1 = "Immunization?patient.identifier=SC378274-MRN|009999997,SC378274-MRN|009999998,SC378274-MRN|009999999&date=2020-01-02";
|
||||||
String immunizationTypeFilter2 = "Immunization?patient=Patient/123";
|
String immunizationTypeFilter2 = "Immunization?patient=Patient/123";
|
||||||
String observationFilter1 = "Observation?subject=Patient/123&created=ge2020-01-01";
|
String observationFilter1 = "Observation?subject=Patient/123&created=ge2020-01-01";
|
||||||
StringBuilder multiValuedTypeFilterBuilder = new StringBuilder()
|
String multiValuedTypeFilterBuilder = "&" +
|
||||||
.append("&")
|
JpaConstants.PARAM_EXPORT_TYPE_FILTER +
|
||||||
.append(JpaConstants.PARAM_EXPORT_TYPE_FILTER)
|
"=" +
|
||||||
.append("=")
|
UrlUtil.escapeUrlParam(immunizationTypeFilter1) +
|
||||||
.append(UrlUtil.escapeUrlParam(immunizationTypeFilter1))
|
"," +
|
||||||
.append(",")
|
UrlUtil.escapeUrlParam(immunizationTypeFilter2) +
|
||||||
.append(UrlUtil.escapeUrlParam(immunizationTypeFilter2))
|
"," +
|
||||||
.append(",")
|
UrlUtil.escapeUrlParam(observationFilter1);
|
||||||
.append(UrlUtil.escapeUrlParam(observationFilter1));
|
|
||||||
|
|
||||||
url += multiValuedTypeFilterBuilder.toString();
|
url += multiValuedTypeFilterBuilder;
|
||||||
|
|
||||||
// call
|
// call
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
myClient.execute(get);
|
try (CloseableHttpResponse ignored = myClient.execute(get)) {
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
BulkExportParameters bp = verifyJobStart();
|
BulkExportParameters bp = verifyJobStart();
|
||||||
assertThat(bp.getFilters(), containsInAnyOrder(immunizationTypeFilter1, immunizationTypeFilter2, observationFilter1));
|
assertThat(bp.getFilters(), containsInAnyOrder(immunizationTypeFilter1, immunizationTypeFilter2, observationFilter1));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitiateGroupExportWithInvalidResourceTypesFails() throws IOException {
|
public void testInitiateGroupExportWithInvalidResourceTypesFails() throws IOException {
|
||||||
// when
|
// when
|
||||||
|
|
||||||
String url = "http://localhost:" + myPort + "/" + "Group/123/" + JpaConstants.OPERATION_EXPORT
|
String url = myServer.getBaseUrl() + "/" + "Group/123/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("StructureDefinition,Observation");
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("StructureDefinition,Observation");
|
||||||
|
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
CloseableHttpResponse execute = myClient.execute(get);
|
try (CloseableHttpResponse execute = myClient.execute(get)) {
|
||||||
String responseBody = IOUtils.toString(execute.getEntity().getContent());
|
String responseBody = IOUtils.toString(execute.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
assertThat(execute.getStatusLine().getStatusCode(), is(equalTo(400)));
|
assertThat(execute.getStatusLine().getStatusCode(), is(equalTo(400)));
|
||||||
assertThat(responseBody, is(containsString("Resource types [StructureDefinition] are invalid for this type of export, as they do not contain search parameters that refer to patients.")));
|
assertThat(responseBody, is(containsString("Resource types [StructureDefinition] are invalid for this type of export, as they do not contain search parameters that refer to patients.")));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitiateGroupExportWithNoResourceTypes() throws IOException {
|
public void testInitiateGroupExportWithNoResourceTypes() throws IOException {
|
||||||
|
@ -614,12 +574,12 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(createJobStartResponse());
|
.thenReturn(createJobStartResponse());
|
||||||
|
|
||||||
// test
|
// test
|
||||||
String url = "http://localhost:" + myPort + "/" + "Group/123/" + JpaConstants.OPERATION_EXPORT
|
String url = myServer.getBaseUrl() + "/" + "Group/123/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON);
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON);
|
||||||
|
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
CloseableHttpResponse execute = myClient.execute(get);
|
try (CloseableHttpResponse execute = myClient.execute(get)) {
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
assertThat(execute.getStatusLine().getStatusCode(), is(equalTo(202)));
|
assertThat(execute.getStatusLine().getStatusCode(), is(equalTo(202)));
|
||||||
|
@ -631,6 +591,7 @@ public class BulkDataExportProviderTest {
|
||||||
() -> assertTrue(bulkExportParameters.getResourceTypes().contains("Device"))
|
() -> assertTrue(bulkExportParameters.getResourceTypes().contains("Device"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitiateWithPostAndMultipleTypeFilters() throws IOException {
|
public void testInitiateWithPostAndMultipleTypeFilters() throws IOException {
|
||||||
|
@ -645,7 +606,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.debug(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
ourLog.debug(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
||||||
|
|
||||||
// call
|
// call
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
ourLog.info("Request: {}", post);
|
ourLog.info("Request: {}", post);
|
||||||
|
@ -654,7 +615,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
|
@ -674,7 +635,7 @@ public class BulkDataExportProviderTest {
|
||||||
input.addParameter(JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT, new StringType(Constants.CT_FHIR_NDJSON));
|
input.addParameter(JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT, new StringType(Constants.CT_FHIR_NDJSON));
|
||||||
|
|
||||||
// call
|
// call
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/Patient/" + JpaConstants.OPERATION_EXPORT);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/Patient/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
ourLog.info("Request: {}", post);
|
ourLog.info("Request: {}", post);
|
||||||
|
@ -682,7 +643,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.info("Response: {}", response.toString());
|
ourLog.info("Response: {}", response.toString());
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BulkExportParameters bp = verifyJobStart();
|
BulkExportParameters bp = verifyJobStart();
|
||||||
|
@ -707,7 +668,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.debug(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
ourLog.debug(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
||||||
|
|
||||||
// call
|
// call
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/Patient/" + JpaConstants.OPERATION_EXPORT);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/Patient/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
ourLog.info("Request: {}", post);
|
ourLog.info("Request: {}", post);
|
||||||
|
@ -715,7 +676,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.info("Response: {}", response.toString());
|
ourLog.info("Response: {}", response.toString());
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BulkExportParameters bp = verifyJobStart();
|
BulkExportParameters bp = verifyJobStart();
|
||||||
|
@ -740,7 +701,7 @@ public class BulkDataExportProviderTest {
|
||||||
input.addParameter(JpaConstants.PARAM_EXPORT_TYPE, new StringType("Patient, Practitioner"));
|
input.addParameter(JpaConstants.PARAM_EXPORT_TYPE, new StringType("Patient, Practitioner"));
|
||||||
|
|
||||||
// call
|
// call
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.addHeader(Constants.HEADER_CACHE_CONTROL, Constants.CACHE_CONTROL_NO_CACHE);
|
post.addHeader(Constants.HEADER_CACHE_CONTROL, Constants.CACHE_CONTROL_NO_CACHE);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
|
@ -749,7 +710,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.info("Response: {}", response.toString());
|
ourLog.info("Response: {}", response.toString());
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
|
@ -757,7 +718,6 @@ public class BulkDataExportProviderTest {
|
||||||
assertThat(parameters.isUseExistingJobsFirst(), is(equalTo(false)));
|
assertThat(parameters.isUseExistingJobsFirst(), is(equalTo(false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProvider_whenEnableBatchJobReuseIsFalse_startsNewJob() throws IOException {
|
public void testProvider_whenEnableBatchJobReuseIsFalse_startsNewJob() throws IOException {
|
||||||
// setup
|
// setup
|
||||||
|
@ -775,7 +735,7 @@ public class BulkDataExportProviderTest {
|
||||||
input.addParameter(JpaConstants.PARAM_EXPORT_TYPE, new StringType("Patient, Practitioner"));
|
input.addParameter(JpaConstants.PARAM_EXPORT_TYPE, new StringType("Patient, Practitioner"));
|
||||||
|
|
||||||
// call
|
// call
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
ourLog.info("Request: {}", post);
|
ourLog.info("Request: {}", post);
|
||||||
|
@ -783,7 +743,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.info("Response: {}", response.toString());
|
ourLog.info("Response: {}", response.toString());
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
|
@ -791,7 +751,6 @@ public class BulkDataExportProviderTest {
|
||||||
assertThat(parameters.isUseExistingJobsFirst(), is(equalTo(false)));
|
assertThat(parameters.isUseExistingJobsFirst(), is(equalTo(false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProviderReturnsSameIdForSameJob() throws IOException {
|
public void testProviderReturnsSameIdForSameJob() throws IOException {
|
||||||
// given
|
// given
|
||||||
|
@ -831,7 +790,7 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(result);
|
.thenReturn(result);
|
||||||
|
|
||||||
// call
|
// call
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
HttpDelete delete = new HttpDelete(url);
|
HttpDelete delete = new HttpDelete(url);
|
||||||
try (CloseableHttpResponse response = myClient.execute(delete)) {
|
try (CloseableHttpResponse response = myClient.execute(delete)) {
|
||||||
|
@ -860,7 +819,7 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(info);
|
.thenReturn(info);
|
||||||
|
|
||||||
// call
|
// call
|
||||||
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
String url = myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
HttpDelete delete = new HttpDelete(url);
|
HttpDelete delete = new HttpDelete(url);
|
||||||
try (CloseableHttpResponse response = myClient.execute(delete)) {
|
try (CloseableHttpResponse response = myClient.execute(delete)) {
|
||||||
|
@ -884,7 +843,7 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(createJobStartResponse());
|
.thenReturn(createJobStartResponse());
|
||||||
|
|
||||||
// call
|
// call
|
||||||
final HttpGet httpGet = new HttpGet(String.format("http://localhost:%s/%s", myPort, JpaConstants.OPERATION_EXPORT));
|
final HttpGet httpGet = new HttpGet(String.format("http://localhost:%s/%s", myServer.getPort(), JpaConstants.OPERATION_EXPORT));
|
||||||
httpGet.addHeader("_outputFormat", Constants.CT_FHIR_NDJSON);
|
httpGet.addHeader("_outputFormat", Constants.CT_FHIR_NDJSON);
|
||||||
httpGet.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
httpGet.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
|
|
||||||
|
@ -892,7 +851,7 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.info("Response: {}", response.toString());
|
ourLog.info("Response: {}", response.toString());
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals(String.format("http://localhost:%s/$export-poll-status?_jobId=%s", myPort, A_JOB_ID), response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(String.format("http://localhost:%s/$export-poll-status?_jobId=%s", myServer.getPort(), A_JOB_ID), response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
assertTrue(IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8).isEmpty());
|
assertTrue(IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8).isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -907,14 +866,14 @@ public class BulkDataExportProviderTest {
|
||||||
.thenReturn(createJobStartResponse());
|
.thenReturn(createJobStartResponse());
|
||||||
|
|
||||||
// call
|
// call
|
||||||
final HttpGet httpGet = new HttpGet(String.format("http://localhost:%s/%s?_outputFormat=%s", myPort, JpaConstants.OPERATION_EXPORT, Constants.CT_FHIR_NDJSON));
|
final HttpGet httpGet = new HttpGet(String.format("http://localhost:%s/%s?_outputFormat=%s", myServer.getPort(), JpaConstants.OPERATION_EXPORT, Constants.CT_FHIR_NDJSON));
|
||||||
httpGet.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
httpGet.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
|
|
||||||
try (CloseableHttpResponse response = myClient.execute(httpGet)) {
|
try (CloseableHttpResponse response = myClient.execute(httpGet)) {
|
||||||
assertAll(
|
assertAll(
|
||||||
() -> assertEquals(202, response.getStatusLine().getStatusCode()),
|
() -> assertEquals(202, response.getStatusLine().getStatusCode()),
|
||||||
() -> assertEquals("Accepted", response.getStatusLine().getReasonPhrase()),
|
() -> assertEquals("Accepted", response.getStatusLine().getReasonPhrase()),
|
||||||
() -> assertEquals(String.format("http://localhost:%s/$export-poll-status?_jobId=%s", myPort, A_JOB_ID), response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue()),
|
() -> assertEquals(String.format("http://localhost:%s/$export-poll-status?_jobId=%s", myServer.getPort(), A_JOB_ID), response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue()),
|
||||||
() -> assertTrue(IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8).isEmpty())
|
() -> assertTrue(IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8).isEmpty())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -933,7 +892,7 @@ public class BulkDataExportProviderTest {
|
||||||
input.addParameter(JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID, new StringType(jobId));
|
input.addParameter(JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID, new StringType(jobId));
|
||||||
|
|
||||||
// Initiate Export Poll Status
|
// Initiate Export Poll Status
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
|
|
||||||
|
@ -962,7 +921,7 @@ public class BulkDataExportProviderTest {
|
||||||
input.addParameter(JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID, new StringType(A_JOB_ID));
|
input.addParameter(JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID, new StringType(A_JOB_ID));
|
||||||
|
|
||||||
// Initiate Export Poll Status
|
// Initiate Export Poll Status
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
|
|
||||||
|
@ -980,7 +939,7 @@ public class BulkDataExportProviderTest {
|
||||||
input.addParameter(JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT, new StringType(ca.uhn.fhir.rest.api.Constants.CT_FHIR_NDJSON));
|
input.addParameter(JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT, new StringType(ca.uhn.fhir.rest.api.Constants.CT_FHIR_NDJSON));
|
||||||
|
|
||||||
// Initiate Export Poll Status
|
// Initiate Export Poll Status
|
||||||
HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS);
|
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
|
|
||||||
|
@ -992,7 +951,7 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
private void callExportAndAssertJobId(Parameters input, String theExpectedJobId) throws IOException {
|
private void callExportAndAssertJobId(Parameters input, String theExpectedJobId) throws IOException {
|
||||||
HttpPost post;
|
HttpPost post;
|
||||||
post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT);
|
post = new HttpPost(myServer.getBaseUrl() + "/" + JpaConstants.OPERATION_EXPORT);
|
||||||
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
post.addHeader(Constants.HEADER_CACHE_CONTROL, Constants.CACHE_CONTROL_NO_CACHE);
|
post.addHeader(Constants.HEADER_CACHE_CONTROL, Constants.CACHE_CONTROL_NO_CACHE);
|
||||||
post.setEntity(new ResourceEntity(myCtx, input));
|
post.setEntity(new ResourceEntity(myCtx, input));
|
||||||
|
@ -1001,9 +960,16 @@ public class BulkDataExportProviderTest {
|
||||||
ourLog.info("Response: {}", response.toString());
|
ourLog.info("Response: {}", response.toString());
|
||||||
assertEquals(202, response.getStatusLine().getStatusCode());
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + theExpectedJobId, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals(myServer.getBaseUrl() + "/$export-poll-status?_jobId=" + theExpectedJobId, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> paramsProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.arguments(true),
|
||||||
|
Arguments.arguments(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue