Merge remote-tracking branch 'origin/master' into ld-20230131-subscription-validating-interceptor-remove-deprecated-methods

This commit is contained in:
Luke deGruchy 2023-02-13 09:54:35 -05:00
commit 0a08c9fbf1
120 changed files with 996 additions and 691 deletions

View File

@ -80,16 +80,12 @@ stages:
module: hapi-fhir-server-mdm
- name: hapi_fhir_server_openapi
module: hapi-fhir-server-openapi
- name: hapi_fhir_serviceloaders
module: hapi-fhir-serviceloaders
- name: hapi_fhir_caching_api
module: hapi-fhir-serviceloaders/hapi-fhir-caching-api
- name: hapi_fhir_caching_caffeine
module: hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine
- name: hapi_fhir_caching_guava
module: hapi-fhir-serviceloaders/hapi-fhir-caching-guava
- name: hapi_fhir_caching_testing
module: hapi-fhir-serviceloaders/hapi-fhir-caching-testing
- name: hapi_fhir_spring_boot_autoconfigure
module: hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure
- name: hapi_fhir_spring_boot_sample_server_jersey

View File

@ -1,10 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -13,9 +14,6 @@
<name>HAPI FHIR - Deployable Artifact Parent POM</name>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
@ -120,27 +118,6 @@
</ignoredResourcePatterns>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<logViolationsToConsole>true</logViolationsToConsole>
<failsOnError>true</failsOnError>
<suppressionsLocation>${maven.multiModuleProjectDirectory}/src/checkstyle/checkstyle_suppressions.xml</suppressionsLocation>
<enableRulesSummary>true</enableRulesSummary>
<enableSeveritySummary>true</enableSeveritySummary>
<consoleOutput>true</consoleOutput>
<configLocation>${maven.multiModuleProjectDirectory}/src/checkstyle/checkstyle.xml</configLocation>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
@ -208,7 +185,6 @@
<execution>
<phase>package</phase>
<goals>
<!--<goal>aggregate-jar</goal>-->
<goal>jar</goal>
</goals>
</execution>
@ -252,5 +228,4 @@
</build>
</profile>
</profiles>
</project>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -196,6 +196,62 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<!--suppress UnresolvedMavenProperty -->
<delete dir="${pom.basedir}/target/" includes="checkstyle*"/>
</target>
</configuration>
<inherited>true</inherited>
<executions>
<execution>
<id>delete-module-cache-file</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven_checkstyle_version}</version>
<configuration>
<!--suppress UnresolvedMavenProperty -->
<configLocation>${maven.multiModuleProjectDirectory}/hapi-fhir-checkstyle/src/checkstyle/hapi-base-checkstyle.xml</configLocation>
<!--suppress UnresolvedMavenProperty -->
<suppressionsLocation>${maven.multiModuleProjectDirectory}/hapi-fhir-checkstyle/src/checkstyle/hapi-base-checkstyle-suppression.xml</suppressionsLocation>
<inputEncoding>UTF-8</inputEncoding>
<consoleOutput>true</consoleOutput>
</configuration>
<inherited>true</inherited>
<executions>
<execution>
<id>hapi-single-module-checkstyle</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-checkstyle</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle_version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
<resources>
<resource>

View File

@ -113,7 +113,9 @@ public enum VersionEnum {
V6_2_5,
// Dev Build
V6_3_0,
V6_4_0
V6_4_0,
V6_5_0,
V6_6_0
;
public static VersionEnum latestVersion() {

View File

@ -333,7 +333,7 @@ public class FhirValidator {
retval.addAll(messages);
}
} catch (InterruptedException | ExecutionException exp) {
throw new InternalErrorException(Msg.code(1975) + exp);
throw new InternalErrorException(Msg.code(2246) + exp);
}
return retval;
}

View File

@ -4,14 +4,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -18,6 +18,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle_version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
@ -34,6 +35,31 @@
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.2.0</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle_version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-checkstyle</artifactId>
<!-- Remember to bump this when you upgrade the version -->
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<!-- For releases, we need to generate javadoc and sources JAR -->
<profile>
@ -75,5 +101,44 @@
</plugins>
</build>
</profile>
<profile>
<id>CI</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven_checkstyle_version}</version>
<configuration>
<excludes>**/osgi/**/*, **/.mvn/**/*, **/.mvn_/**/*</excludes>
<sourceDirectories>
<!--scan base project directory to include all modules-->
<!--suppress UnresolvedMavenProperty -->
<directory>${maven.multiModuleProjectDirectory}/</directory>
</sourceDirectories>
<!--suppress UnresolvedMavenProperty -->
<configLocation>${maven.multiModuleProjectDirectory}/hapi-fhir-checkstyle/src/checkstyle/hapi-base-checkstyle.xml</configLocation>
<!--suppress UnresolvedMavenProperty -->
<suppressionsLocation>${maven.multiModuleProjectDirectory}/hapi-fhir-checkstyle/src/checkstyle/hapi-base-checkstyle-suppression.xml</suppressionsLocation>
<inputEncoding>UTF-8</inputEncoding>
<consoleOutput>true</consoleOutput>
</configuration>
<executions>
<execution>
<id>checkstyle-across-all-modules</id>
<phase>install</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
<execution>
<id>hapi-single-module-checkstyle</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -1,10 +1,18 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Puppy Crawl//DTD Suppressions 1.1//EN"
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<suppress files="[/\\]test[/\\]" checks="[a-zA-Z0-9]*" />
<suppress files="[/\\]resources[/\\]" checks="[a-zA-Z0-9]*" />
<suppress files="[\\/]target[\\/]" checks="[a-zA-Z0-9]*" />
<suppress files="[\\/]hapi-fhir-docs[\\/]" checks="[a-zA-Z0-9]*" />
<suppress files="[\\/]hapi-fhir-checkstyle[\\/]" checks="[a-zA-Z0-9]*" />
<suppress files="[\\/]hapi-fhir-jpaserver-test-utilities[\\/]" checks="[a-zA-Z0-9]*" />
<suppress files="[\\/]hapi-tinder-plugin[\\/]" checks="[a-zA-Z0-9]*" />
<suppress files="[\\/]osgi[\\/]" checks="[a-zA-Z0-9]*"/>
<!-- Name suppressions -->
<suppress files="Base64BinaryDt\.java" checks="AbstractClassName"/>
<!-- TODO KHS cdr instantiates these. Remove them once cdr has been fixed. -->
@ -16,4 +24,6 @@
<suppress files="RequestDetails\.java" checks="AbstractClassName"/>
<suppress files="RestfulClientFactory\.java" checks="AbstractClassName"/>
<suppress files="MatchUrlService\.java" checks="AbstractClassName"/>
<suppress files="BaseController\.java" checks="AbstractClassName"/>
</suppressions>

View File

@ -0,0 +1,41 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<property name="severity" value="error"/>
<property name="charset" value="UTF-8"/>
<property name="fileExtensions" value="java, properties, xml, js, json"/>
<module name="TreeWalker">
<!-- Run custom HapiErrorCodeCheck to find duplicate error codes -->
<module name="ca.uhn.fhir.checks.HapiErrorCodeCheck"/>
<!-- Throw error if any FIX ME is left in the code -->
<module name="TodoComment">
<!-- The (?i) below means Case Insensitive -->
<property name="format" value="(?i)FIXME"/>
</module>
<module name="RegexpSinglelineJava">
<property name="format" value="System\.out\.println"/>
<property name="ignoreComments" value="true"/>
</module>
<module name="RegexpSinglelineJava">
<property name="format" value="org\.jetbrains\.annotations\.NotNull"/>
</module>
<module name="RegexpSinglelineJava">
<property name="format" value="org\.jetbrains\.annotations\.Nullable"/>
</module>
<!-- Should always use the Spring transactional interface, per: https://stackoverflow.com/questions/26387399/javax-transaction-transactional-vs-org-springframework-transaction-annotation-tr -->
<module name="RegexpSinglelineJava">
<property name="format" value="javax\.transaction\.Transactional"/>
<property name="message"
value="Wrong @Transactional annotation used, use instead: org.springframework.transaction.annotation.Transactional"/>
</module>
<module name="AbstractClassName">
<property name="format" value="^(Base|Abstract).+$"/>
</module>
</module>
</module>

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.checks;
import com.puppycrawl.tools.checkstyle.StatelessCheck;
import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
@ -9,15 +9,13 @@ import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* mvn -P CI checkstyle:check
*/
@StatelessCheck
@FileStatefulCheck
public final class HapiErrorCodeCheck extends AbstractCheck {
private static final Logger ourLog = LoggerFactory.getLogger(HapiErrorCodeCheck.class);
private static final Map<Integer, String> ourCodesUsed = new HashMap<>();
private static final ErrorCodeCache ourCache = ErrorCodeCache.INSTANCE;
@Override
public int[] getDefaultTokens() {
@ -42,10 +40,6 @@ public final class HapiErrorCodeCheck extends AbstractCheck {
}
private void validateMessageCode(DetailAST theAst) {
// TODO KHS this should be done in the checkstyle plugin pom config, not here
if (getFileContents().getFileName().contains("/generated-sources/")) {
return;
}
DetailAST instantiation = theAst.getFirstChild().getFirstChild();
// We only expect message codes on new exception instantiations
if (TokenTypes.LITERAL_NEW != instantiation.getType()) {
@ -68,14 +62,13 @@ public final class HapiErrorCodeCheck extends AbstractCheck {
DetailAST numberNode = msgNode.getParent().getNextSibling().getFirstChild().getFirstChild();
if (TokenTypes.NUM_INT == numberNode.getType()) {
Integer code = Integer.valueOf(numberNode.getText());
if (ourCodesUsed.containsKey(code)) {
if (ourCache.containsKey(code)) {
log(theAst.getLineNo(), "Two different exception messages call Msg.code(" +
code +
"). Each thrown exception throw call Msg.code() with a different code. " +
"Previously found at: " + ourCodesUsed.get(code));
code + "). \nEach thrown exception must call Msg.code() with a different code. " +
"\nPreviously found at: " + ourCache.get(code));
} else {
String location = getFileContents().getFileName() + ":" + instantiation.getLineNo() + ":" + instantiation.getColumnNo() + "(" + code + ")";
ourCodesUsed.put(code, location);
String location = getFilePath() + ":" + instantiation.getLineNo() + ":" + instantiation.getColumnNo() + "(" + code + ")";
ourCache.put(code, location);
}
} else {
log(theAst.getLineNo(), "Called Msg.code() with a non-integer argument");
@ -110,5 +103,30 @@ public final class HapiErrorCodeCheck extends AbstractCheck {
}
return null;
}
public enum ErrorCodeCache {
INSTANCE;
private static final Map<Integer, String> ourCodesUsed = new HashMap<>();
ErrorCodeCache() {
}
public boolean containsKey(Integer s) {
return ourCodesUsed.containsKey(s);
}
public String get(Integer i) {
return ourCodesUsed.get(i);
}
public String put(Integer code, String location) {
return ourCodesUsed.put(code, location);
}
public Set<Integer> keySet() {
return ourCodesUsed.keySet();
}
}
}

View File

@ -44,12 +44,13 @@ class HapiErrorCodeCheckTest {
// validate
String[] errorLines = errors.toString().split("\r?\n");
Arrays.stream(errorLines).forEach(ourLog::info);
assertEquals(2, errorLines.length);
assertEquals(4, errorLines.length);
assertThat(errorLines[0], startsWith("[ERROR] "));
assertThat(errorLines[0], endsWith("BadClass.java:7: Exception thrown that does not call Msg.code() [HapiErrorCode]"));
assertThat(errorLines[1], startsWith("[ERROR] "));
assertThat(errorLines[1], containsString("BadClass.java:11: Two different exception messages call Msg.code(2). Each thrown exception throw call Msg.code() with a different code."));
assertThat(errorLines[1], containsString("BadClass.java:9:9"));
assertThat(errorLines[1], containsString("Two different exception messages call Msg.code(2258)."));
assertThat(errorLines[2], containsString("Each thrown exception must call Msg.code() with a different code."));
assertThat(errorLines[3], containsString("Previously found at:"));
}
private Checker buildChecker() throws CheckstyleException {

View File

@ -2,16 +2,15 @@ public class BadClass {
public void init() throws Exception {
int i = 1;
if (i == 0) {
throw new MessagingException(theMessage, Msg.code(6) + "Failure handling subscription payload", e);
throw new MessagingException(theMessage, Msg.code(2259) + "Failure handling subscription payload", e);
} else if (i == 1) {
throw new RuntimeException("nocode");
} else if (i == 2) {
throw new RuntimeException(Msg.code(2) + "duplicate code");
throw new RuntimeException(Msg.code(2258) + "duplicate code");
} else if (i == 3) {
throw new RuntimeException(Msg.code(2) + "duplicate code");
} else if (i == 4) {
throw new RuntimeException(Msg.code(1) + "good");
throw new RuntimeException(Msg.code(2258) + "duplicate code");
}
ClassCastException e = new ClassCastException();
throwException(i, e);
}

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -531,7 +531,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
return Optional.of(new TlsAuthentication(keyStoreInfo, trustStoreInfo));
}
catch(Exception e){
throw new RuntimeException(Msg.code(2115)+"Could not create TLS configuration options", e);
throw new RuntimeException(Msg.code(2253)+"Could not create TLS configuration options", e);
}
}

View File

@ -48,7 +48,7 @@ public class ValidationSupportChainCreator {
chain.addValidationSupport(localFileValidationSupport);
chain.addValidationSupport(new SnapshotGeneratingValidationSupport(ctx));
} catch (IOException e) {
throw new RuntimeException(Msg.code(2207) + "Failed to load local profile.", e);
throw new RuntimeException(Msg.code(2254) + "Failed to load local profile.", e);
}
}
if (commandLine.hasOption("r")) {

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,7 @@
---
type: fix
issue: 4528
title: "Three database columns have been changed from type TEXT to type OID
when running in Postgres:
BT2_JOB_INSTANCE.PARAMS_JSON_LOB, BT2_JOB_INSTANCE.REPORT, and BT2_WORK_CHUNK.CHUNK_DATA.
This prevents VACUUM erasing binary objects that are still in use."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 4483
title: "The OverridePathBasedReferentialIntegrityForDeletesInterceptor now works on partitioned
servers. Thanks to GitHub user @JorisHeadease for the pull request!"

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 4509
title: "References to the patient/subject in IPS documents generated using the $summary
operation were not replaced with the bundle-local UUID assigned to the patient. Also,
some dangling references were left in the generated bundle. This has been corrected."

View File

@ -0,0 +1,3 @@
---
release-date: "2023-05-18"
codename: "TBD"

View File

@ -74,6 +74,37 @@ myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/ValueSet/cats
myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/mysystem-vs-*");
```
## Referential Integrity
Enabling referential integrity will ensure that reference values exist in the database. If the referenced entity does
not exist, the server will return an error.
It is important to note that referential integrity is not enforced on database-level. The referential integrity check
*only* validates references that are indexed by a `SearchParameter`.
### Enabling Referential Integrity
Referential integrity can be configured on two levels: `write` and `delete`.
#### JPA Server
```java
@Bean
public DaoConfig daoConfig() {
DaoConfig retVal = new DaoConfig();
// ... other config ...
retVal.setEnforceReferentialIntegrityOnWrite(true);
retVal.setEnforceReferentialIntegrityOnDelete(true);
return retVal;
}
```
#### JPA Server Starter
This can be easily enabled in the `application.yaml` file at the following paths:
```yaml
hapi:
fhir:
enforce_referential_integrity_on_write: true
enforce_referential_integrity_on_delete: true
```
# Search Result Caching
By default, search results will be cached for one minute. This means that if a client performs a search for <code>Patient?name=smith</code> and gets back 500 results, if a client performs the same search within 60000 milliseconds the previously loaded search results will be returned again. This also means that any new Patient resources named "Smith" within the last minute will not be reflected in the results.

View File

@ -60,4 +60,4 @@ Built-in Narrative Templates:
# Credits
This module is based on the excellent work of Rio Bennin and Panayiotis Savva of the University of Cyprus.
This module is based on the excellent work of Rio Bennin of Crossroads Labs, and Panayiotis Savva and Constantinos Yiasemi of the University of Cyprus.

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.util;
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.util.StopWatch;
import com.google.common.collect.Queues;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -549,7 +549,7 @@ public class JpaBulkExportProcessor implements IBulkExportProcessor<JpaPid> {
Optional<RuntimeSearchParam> oPatientSearchParam = SearchParameterUtil.getOnlyPatientSearchParamForResourceType(myContext, theResource.fhirType());
if (!oPatientSearchParam.isPresent()) {
String errorMessage = String.format("[%s] has no search parameters that are for patients, so it is invalid for Group Bulk Export!", theResource.fhirType());
throw new IllegalArgumentException(Msg.code(2103) + errorMessage);
throw new IllegalArgumentException(Msg.code(2242) + errorMessage);
} else {
return oPatientSearchParam.get();
}

View File

@ -452,7 +452,7 @@ public class ExtendedHSearchClauseBuilder {
booleanStep.minimumShouldMatchNumber(1);
return booleanStep;
}
throw new IllegalArgumentException(Msg.code(2025) + "Date search param does not support prefix of type: " + prefix);
throw new IllegalArgumentException(Msg.code(2255) + "Date search param does not support prefix of type: " + prefix);
}
private PredicateFinalStep generateDateInstantSearchTerms(DateParam theDateParam, PathContext theSpContext) {
@ -496,7 +496,7 @@ public class ExtendedHSearchClauseBuilder {
return ((SearchPredicateFactory) theSpContext).range().field(lowerInstantField).atMost(upperBoundAsInstant);
}
throw new IllegalArgumentException(Msg.code(2026) + "Date search param does not support prefix of type: " + prefix);
throw new IllegalArgumentException(Msg.code(2256) + "Date search param does not support prefix of type: " + prefix);
}

View File

@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.model.DeleteConflict;
import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
@ -99,7 +100,7 @@ public class OverridePathBasedReferentialIntegrityForDeletesInterceptor {
* Interceptor hook method. Do not invoke directly.
*/
@Hook(value = Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, order = CascadingDeleteInterceptor.OVERRIDE_PATH_BASED_REF_INTEGRITY_INTERCEPTOR_ORDER)
public void handleDeleteConflicts(DeleteConflictList theDeleteConflictList) {
public void handleDeleteConflicts(DeleteConflictList theDeleteConflictList, RequestDetails requestDetails) {
for (DeleteConflict nextConflict : theDeleteConflictList) {
ourLog.info("Ignoring referential integrity deleting {} - Referred to from {} at path {}", nextConflict.getTargetId(), nextConflict.getSourceId(), nextConflict.getSourcePath());
@ -107,7 +108,7 @@ public class OverridePathBasedReferentialIntegrityForDeletesInterceptor {
IdDt targetId = nextConflict.getTargetId();
String targetIdValue = targetId.toVersionless().getValue();
IBaseResource sourceResource = myDaoRegistry.getResourceDao(sourceId.getResourceType()).read(sourceId);
IBaseResource sourceResource = myDaoRegistry.getResourceDao(sourceId.getResourceType()).read(sourceId, requestDetails);
IFhirPath fhirPath = myFhirContext.newFhirPath();
for (String nextPath : myPaths) {

View File

@ -113,6 +113,18 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.online(true)
.withColumns("SEARCH_PID")
.onlyAppliesToPlatforms(NON_AUTOMATIC_FK_INDEX_PLATFORMS);
// fix Postgres clob types - that stupid oid driver problem is still there
// BT2_JOB_INSTANCE.PARAMS_JSON_LOB
version.onTable("BT2_JOB_INSTANCE")
.migratePostgresTextClobToBinaryClob("20230208.1", "PARAMS_JSON_LOB");
// BT2_JOB_INSTANCE.REPORT
version.onTable("BT2_JOB_INSTANCE")
.migratePostgresTextClobToBinaryClob("20230208.2", "REPORT");
// BT2_WORK_CHUNK.CHUNK_DATA
version.onTable("BT2_WORK_CHUNK")
.migratePostgresTextClobToBinaryClob("20230208.3", "CHUNK_DATA");
}
private void init620() {

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.ips.generator;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
@ -32,6 +33,7 @@ import ca.uhn.fhir.jpa.ips.api.SectionRegistry;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -42,6 +44,10 @@ import ca.uhn.fhir.util.BundleBuilder;
import ca.uhn.fhir.util.CompositionBuilder;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.ValidateUtil;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseExtension;
@ -66,6 +72,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -114,16 +121,18 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
}
private IBaseBundle generateIpsForPatient(RequestDetails theRequestDetails, IBaseResource thePatient) {
IIdType originalSubjectId = myFhirContext.getVersion().newIdType().setValue(thePatient.getIdElement().getValue());
IIdType originalSubjectId = myFhirContext.getVersion().newIdType().setValue(thePatient.getIdElement().getValue()).toUnqualifiedVersionless();
massageResourceId(null, thePatient);
IpsContext context = new IpsContext(thePatient, originalSubjectId);
ResourceInclusionCollection globalResourcesToInclude = new ResourceInclusionCollection();
globalResourcesToInclude.addResourceIfNotAlreadyPresent(thePatient, originalSubjectId.getValue());
IBaseResource author = myGenerationStrategy.createAuthor();
massageResourceId(context, author);
CompositionBuilder compositionBuilder = createComposition(thePatient, context, author);
ResourceInclusionCollection globalResourcesToInclude = determineInclusions(theRequestDetails, originalSubjectId, context, compositionBuilder);
determineInclusions(theRequestDetails, originalSubjectId, context, compositionBuilder, globalResourcesToInclude);
IBaseResource composition = compositionBuilder.getComposition();
@ -131,10 +140,10 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(globalResourcesToInclude);
generator.populateResourceNarrative(myFhirContext, composition);
return createCompositionDocument(thePatient, author, composition, globalResourcesToInclude);
return createCompositionDocument(author, composition, globalResourcesToInclude);
}
private IBaseBundle createCompositionDocument(IBaseResource thePatient, IBaseResource author, IBaseResource composition, ResourceInclusionCollection theResourcesToInclude) {
private IBaseBundle createCompositionDocument(IBaseResource author, IBaseResource composition, ResourceInclusionCollection theResourcesToInclude) {
BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext);
bundleBuilder.setType(Bundle.BundleType.DOCUMENT.toCode());
bundleBuilder.setIdentifier("urn:ietf:rfc:4122", UUID.randomUUID().toString());
@ -143,9 +152,6 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
// Add composition to document
bundleBuilder.addDocumentEntry(composition);
// Add subject to document
bundleBuilder.addDocumentEntry(thePatient);
// Add inclusion candidates
for (IBaseResource next : theResourcesToInclude.getResources()) {
bundleBuilder.addDocumentEntry(next);
@ -158,13 +164,12 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
}
@Nonnull
private ResourceInclusionCollection determineInclusions(RequestDetails theRequestDetails, IIdType originalSubjectId, IpsContext context, CompositionBuilder theCompositionBuilder) {
ResourceInclusionCollection globalResourcesToInclude = new ResourceInclusionCollection();
private ResourceInclusionCollection determineInclusions(RequestDetails theRequestDetails, IIdType originalSubjectId, IpsContext context, CompositionBuilder theCompositionBuilder, ResourceInclusionCollection theGlobalResourcesToInclude) {
SectionRegistry sectionRegistry = myGenerationStrategy.getSectionRegistry();
for (SectionRegistry.Section nextSection : sectionRegistry.getSections()) {
determineInclusionsForSection(theRequestDetails, originalSubjectId, context, theCompositionBuilder, globalResourcesToInclude, nextSection);
determineInclusionsForSection(theRequestDetails, originalSubjectId, context, theCompositionBuilder, theGlobalResourcesToInclude, nextSection);
}
return globalResourcesToInclude;
return theGlobalResourcesToInclude;
}
private void determineInclusionsForSection(RequestDetails theRequestDetails, IIdType theOriginalSubjectId, IpsContext theIpsContext, CompositionBuilder theCompositionBuilder, ResourceInclusionCollection theGlobalResourcesToInclude, SectionRegistry.Section theSection) {
@ -216,6 +221,8 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
nextCandidate = previouslyExistingResource;
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
} else if (theGlobalResourcesToInclude.hasResourceWithReplacementId(originalResourceId)) {
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
} else {
IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, nextCandidate);
nextCandidate.setId(id);
@ -228,26 +235,6 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
}
/*
* Update any references within the added candidates - This is important
* because we might be replacing resource IDs before including them in
* the summary, so we need to also update the references to those
* resources.
*/
for (IBaseResource nextResource : sectionResourcesToInclude.getResources()) {
List<ResourceReferenceInfo> references = myFhirContext.newTerser().getAllResourceReferences(nextResource);
for (ResourceReferenceInfo nextReference : references) {
String existingReference = nextReference.getResourceReference().getReferenceElement().getValue();
if (isNotBlank(existingReference)) {
existingReference = new IdType(existingReference).toUnqualifiedVersionless().getValue();
String replacement = theGlobalResourcesToInclude.getIdSubstitution(existingReference);
if (isNotBlank(replacement) && !replacement.equals(existingReference)) {
nextReference.getResourceReference().setReference(replacement);
}
}
}
}
}
if (sectionResourcesToInclude.isEmpty() && theSection.getNoInfoGenerator() != null) {
@ -261,6 +248,33 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(noInfoResource, id);
}
/*
* Update any references within the added candidates - This is important
* because we might be replacing resource IDs before including them in
* the summary, so we need to also update the references to those
* resources.
*/
for (IBaseResource nextResource : sectionResourcesToInclude.getResources()) {
List<ResourceReferenceInfo> references = myFhirContext.newTerser().getAllResourceReferences(nextResource);
for (ResourceReferenceInfo nextReference : references) {
String existingReference = nextReference.getResourceReference().getReferenceElement().getValue();
if (isNotBlank(existingReference)) {
existingReference = new IdType(existingReference).toUnqualifiedVersionless().getValue();
String replacement = theGlobalResourcesToInclude.getIdSubstitution(existingReference);
if (isNotBlank(replacement)) {
if (!replacement.equals(existingReference)) {
nextReference.getResourceReference().setReference(replacement);
}
} else if (theGlobalResourcesToInclude.getResourceById(existingReference) == null) {
// If this reference doesn't point to something we have actually
// included in the bundle, clear the reference.
nextReference.getResourceReference().setReference(null);
nextReference.getResourceReference().setResource(null);
}
}
}
}
addSection(theSection, theCompositionBuilder, sectionResourcesToInclude, theGlobalResourcesToInclude);
}
@ -273,6 +287,9 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
sectionBuilder.addCodeCoding(LOINC_URI, theSection.getSectionCode(), theSection.getSectionDisplay());
for (IBaseResource next : theResourcesToInclude.getResources()) {
if (ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next) == BundleEntrySearchModeEnum.INCLUDE) {
continue;
}
IBaseExtension<?, ?> narrativeLink = ((IBaseHasExtensions) next).addExtension();
narrativeLink.setUrl("http://hl7.org/fhir/StructureDefinition/narrativeLink");
@ -309,7 +326,18 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
private String determinePatientCompartmentSearchParameterName(String theResourceType) {
RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType);
return resourceDef.getSearchParamsForCompartmentName("Patient").get(0).getName();
Set<String> searchParams = resourceDef.getSearchParamsForCompartmentName("Patient")
.stream()
.map(RuntimeSearchParam::getName)
.collect(Collectors.toSet());
// Prefer "patient", then "subject" then anything else
if (searchParams.contains(Observation.SP_PATIENT)) {
return Observation.SP_PATIENT;
}
if (searchParams.contains(Observation.SP_SUBJECT)) {
return Observation.SP_SUBJECT;
}
return searchParams.iterator().next();
}
private void massageResourceId(IpsContext theIpsContext, IBaseResource theResource) {
@ -514,7 +542,7 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
private final List<IBaseResource> myResources = new ArrayList<>();
private final Map<String, IBaseResource> myIdToResource = new HashMap<>();
private final Map<String, String> myOriginalIdToNewId = new HashMap<>();
private final BiMap<String, String> myOriginalIdToNewId = HashBiMap.create();
public List<IBaseResource> getResources() {
return myResources;
@ -541,7 +569,15 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
}
public IBaseResource getResourceById(IIdType theReference) {
return myIdToResource.get(theReference.toUnqualifiedVersionless().getValue());
return getResourceById(theReference.toUnqualifiedVersionless().getValue());
}
public boolean hasResourceWithReplacementId(String theReplacementId) {
return myOriginalIdToNewId.containsValue(theReplacementId);
}
public IBaseResource getResourceById(String theReference) {
return myIdToResource.get(theReference);
}
@Nullable

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.ips.generator;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
@ -8,8 +9,16 @@ import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.MedicationStatement;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -18,7 +27,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ContextConfiguration(classes = {IpsGenerationTest.IpsConfig.class})
public class IpsGenerationTest extends BaseResourceProviderR4Test {
@ -34,6 +49,7 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
@AfterEach
public void afterEach() {
myServer.withServer(t -> t.unregisterProvider(myIpsOperationProvider));
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC);
}
@ -55,12 +71,55 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
.withNoParameters(Parameters.class)
.returnResourceType(Bundle.class)
.execute();
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(37, output.getEntry().size());
// Verify
validateDocument(outcome);
assertEquals(117, output.getEntry().size());
String patientId = findFirstEntryResource(output, Patient.class, 1).getId();
assertThat(patientId, matchesPattern("urn:uuid:.*"));
MedicationStatement medicationStatement = findFirstEntryResource(output, MedicationStatement.class, 2);
assertEquals(patientId, medicationStatement.getSubject().getReference());
assertNull(medicationStatement.getInformationSource().getReference());
}
@Test
public void testGenerateTinyPatientSummary() {
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/tiny-patient-everything.json.gz");
sourceData.setType(Bundle.BundleType.TRANSACTION);
for (Bundle.BundleEntryComponent nextEntry : sourceData.getEntry()) {
nextEntry.getRequest().setMethod(Bundle.HTTPVerb.PUT);
nextEntry.getRequest().setUrl(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
Bundle outcome = mySystemDao.transaction(mySrd, sourceData);
ourLog.info("Created {} resources", outcome.getEntry().size());
Bundle output = myClient
.operation()
.onInstance("Patient/5342998")
.named(JpaConstants.OPERATION_SUMMARY)
.withNoParameters(Parameters.class)
.returnResourceType(Bundle.class)
.execute();
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
// Verify
validateDocument(outcome);
assertEquals(7, output.getEntry().size());
String patientId = findFirstEntryResource(output, Patient.class, 1).getId();
assertThat(patientId, matchesPattern("urn:uuid:.*"));
assertEquals(patientId, findEntryResource(output, Condition.class, 0, 2).getSubject().getReference());
assertEquals(patientId, findEntryResource(output, Condition.class, 1, 2).getSubject().getReference());
}
private void validateDocument(Bundle theOutcome) {
FhirValidator validator = myFhirContext.newValidator();
validator.registerValidatorModule(new FhirInstanceValidator(myFhirContext));
ValidationResult validation = validator.validateWithResult(theOutcome);
assertTrue(validation.isSuccessful(), () -> myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(validation.toOperationOutcome()));
}
@Configuration
public static class IpsConfig {
@ -83,5 +142,21 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
}
@SuppressWarnings("unchecked")
private static <T extends IBaseResource> T findFirstEntryResource(Bundle theBundle, Class<T> theType, int theExpectedCount) {
return findEntryResource(theBundle, theType, 0, theExpectedCount);
}
@SuppressWarnings("unchecked")
static <T extends IBaseResource> T findEntryResource(Bundle theBundle, Class<T> theType, int index, int theExpectedCount) {
List<Resource> resources = theBundle
.getEntry()
.stream()
.map(Bundle.BundleEntryComponent::getResource)
.filter(r -> theType.isAssignableFrom(r.getClass()))
.toList();
assertEquals(theExpectedCount, resources.size());
return (T) resources.get(index);
}
}

View File

@ -33,6 +33,7 @@ import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Device;
import org.hl7.fhir.r4.model.DeviceUseStatement;
import org.hl7.fhir.r4.model.DiagnosticReport;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Immunization;
import org.hl7.fhir.r4.model.Medication;
@ -60,12 +61,15 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.ips.generator.IpsGenerationTest.findEntryResource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@ -74,6 +78,8 @@ public class IpsGeneratorSvcImplTest {
public static final String MEDICATION_ID = "Medication/tyl";
public static final String MEDICATION_STATEMENT_ID = "MedicationStatement/meds";
public static final String MEDICATION_STATEMENT_ID2 = "MedicationStatement/meds2";
public static final String PATIENT_ID = "Patient/123";
public static final String ENCOUNTER_ID = "Encounter/encounter";
private static final List<Class<? extends IBaseResource>> RESOURCE_TYPES = Lists.newArrayList(
AllergyIntolerance.class,
CarePlan.class,
@ -157,7 +163,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123"));
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify Bundle Contents
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
@ -170,12 +176,7 @@ public class IpsGeneratorSvcImplTest {
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = compositions
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICATION_SUMMARY).getTitle()))
.findFirst()
.orElseThrow();
Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@ -191,6 +192,17 @@ public class IpsGeneratorSvcImplTest {
assertThat(row.getCell(4).asNormalizedText(), containsString("2023"));
}
@Nonnull
private Composition.SectionComponent findSection(Composition compositions, IpsSectionEnum sectionEnum) {
Composition.SectionComponent section = compositions
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(sectionEnum).getTitle()))
.findFirst()
.orElseThrow();
return section;
}
@Test
public void testMedicationSummary_DuplicateSecondaryResources() {
myStrategy.setSectionRegistry(new SectionRegistry().addGlobalCustomizer(t -> t.withNoInfoGenerator(null)));
@ -209,7 +221,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123"));
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify Bundle Contents
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
@ -248,7 +260,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123"));
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify Bundle Contents
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
@ -263,12 +275,7 @@ public class IpsGeneratorSvcImplTest {
// Verify narrative - should have 2 rows (one for each primary MedicationStatement)
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = compositions
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICATION_SUMMARY).getTitle()))
.findFirst()
.orElseThrow();
Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@ -304,16 +311,11 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123"));
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = compositions
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICAL_DEVICES).getTitle()))
.findFirst()
.orElseThrow();
Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICAL_DEVICES);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@ -356,16 +358,11 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123"));
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = compositions
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.IMMUNIZATIONS).getTitle()))
.findFirst()
.orElseThrow();
Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.IMMUNIZATIONS);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@ -383,10 +380,82 @@ public class IpsGeneratorSvcImplTest {
assertThat(row.getCell(6).asNormalizedText(), containsString("2023"));
}
@Test
public void testReferencesUpdatedInSecondaryInclusions() {
// Setup Patient
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
Encounter encounter = new Encounter();
encounter.setId(new IdType(ENCOUNTER_ID));
encounter.setSubject(new Reference(PATIENT_ID));
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(encounter, BundleEntrySearchModeEnum.INCLUDE);
Condition conditionActive = new Condition();
conditionActive.setId("Condition/conditionActive");
conditionActive.getClinicalStatus().addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")
.setCode("active");
conditionActive.setSubject(new Reference(PATIENT_ID));
conditionActive.setEncounter(new Reference(ENCOUNTER_ID));
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(conditionActive, BundleEntrySearchModeEnum.MATCH);
Condition conditionResolved = new Condition();
conditionResolved.setId("Condition/conditionResolved");
conditionResolved.getClinicalStatus().addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")
.setCode("resolved");
conditionResolved.setSubject(new Reference(PATIENT_ID));
conditionResolved.setEncounter(new Reference(ENCOUNTER_ID));
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(conditionResolved, BundleEntrySearchModeEnum.MATCH);
// Conditions will be loaded from two sections (problem list and illness history) so
// we return an active condition the first time and a resolved one the second
IFhirResourceDao<Condition> conditionDao = registerResourceDaoWithNoData(Condition.class);
when(conditionDao.search(any(), any())).thenReturn(
new SimpleBundleProvider(Lists.newArrayList(conditionActive, encounter)),
new SimpleBundleProvider(Lists.newArrayList(conditionResolved, encounter))
);
registerRemainingResourceDaos();
// Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify cross-references
Patient addedPatient = findEntryResource(outcome, Patient.class, 0, 1);
assertThat(addedPatient.getId(), startsWith("urn:uuid:"));
Condition addedCondition = findEntryResource(outcome, Condition.class, 0, 2);
assertThat(addedCondition.getId(), startsWith("urn:uuid:"));
Condition addedCondition2 = findEntryResource(outcome, Condition.class, 1, 2);
assertThat(addedCondition2.getId(), startsWith("urn:uuid:"));
Encounter addedEncounter = findEntryResource(outcome, Encounter.class, 0, 1);
assertThat(addedEncounter.getId(), startsWith("urn:uuid:"));
MedicationStatement addedMedicationStatement = findEntryResource(outcome, MedicationStatement.class, 0, 1);
assertThat(addedMedicationStatement.getId(), startsWith("urn:uuid:"));
assertEquals("no-medication-info", addedMedicationStatement.getMedicationCodeableConcept().getCodingFirstRep().getCode());
assertEquals(addedPatient.getId(), addedCondition.getSubject().getReference());
assertEquals(addedEncounter.getId(), addedCondition.getEncounter().getReference());
assertEquals(addedPatient.getId(), addedEncounter.getSubject().getReference());
assertEquals(addedPatient.getId(), addedMedicationStatement.getSubject().getReference());
// Verify sections
ourLog.info("Resource: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
verify(conditionDao, times(2)).search(any(), any());
Composition composition = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent problemListSection = findSection(composition, IpsSectionEnum.PROBLEM_LIST);
assertEquals(addedCondition.getId(), problemListSection.getEntry().get(0).getReference());
assertEquals(1, problemListSection.getEntry().size());
Composition.SectionComponent illnessHistorySection = findSection(composition, IpsSectionEnum.ILLNESS_HISTORY);
assertEquals(addedCondition2.getId(), illnessHistorySection.getEntry().get(0).getReference());
assertEquals(1, illnessHistorySection.getEntry().size());
}
private void registerPatientDaoWithRead() {
IFhirResourceDao<Patient> patientDao = registerResourceDaoWithNoData(Patient.class);
Patient patient = new Patient();
patient.setId("Patient/123");
patient.setId(PATIENT_ID);
when(patientDao.read(any(), any())).thenReturn(patient);
}
@ -432,12 +501,11 @@ public class IpsGeneratorSvcImplTest {
@Nonnull
private static List<String> toEntryResourceTypeStrings(Bundle outcome) {
List<String> contentResourceTypes = outcome
return outcome
.getEntry()
.stream()
.map(t -> t.getResource().getResourceType().name())
.collect(Collectors.toList());
return contentResourceTypes;
}
@Nonnull

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,145 @@
package ca.uhn.fhir.jpa.interceptor;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.provider.r4.BaseMultitenantResourceProviderR4Test;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import org.hl7.fhir.r4.model.AuditEvent;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
/**
* <p>Multitenant test version of {@link OverridePathBasedReferentialIntegrityForDeletesInterceptorTest}.</p>
*
* <p>Ensures the tenant is properly propagated down to the {@link ca.uhn.fhir.jpa.api.dao.DaoRegistry} when requesting
* the conflicting resource.</p>
*
* <p>This test runs a subset of tests from {@link OverridePathBasedReferentialIntegrityForDeletesInterceptorTest}
* against the {@link BaseMultitenantResourceProviderR4Test}</p>
*/
public class MultitenantOverridePathBasedReferentialIntegrityForDeletesInterceptorTest extends BaseMultitenantResourceProviderR4Test {
@Autowired
private OverridePathBasedReferentialIntegrityForDeletesInterceptor mySvc;
@Autowired
private CascadingDeleteInterceptor myCascadingDeleteInterceptor;
RequestDetails requestDetails = new SystemRequestDetails();
@BeforeEach
public void beforeEach() {
requestDetails.setTenantId(TENANT_A);
}
@AfterEach
public void after() throws Exception {
myPartitionSettings.setAllowReferencesAcrossPartitions(PartitionSettings.CrossPartitionReferenceMode.NOT_ALLOWED);
assertFalse(myPartitionSettings.isAllowUnqualifiedCrossPartitionReference());
myInterceptorRegistry.unregisterInterceptor(mySvc);
mySvc.clearPaths();
super.after();
}
@Test
public void testAllowDelete() {
mySvc.addPath("AuditEvent.agent.who");
myInterceptorRegistry.registerInterceptor(mySvc);
Patient patient = new Patient();
patient.setId("P");
patient.setActive(true);
myPatientDao.update(patient, requestDetails);
AuditEvent audit = new AuditEvent();
audit.setId("A");
audit.addAgent().getWho().setReference("Patient/P");
myAuditEventDao.update(audit, requestDetails);
// Delete should proceed
myPatientDao.delete(new IdType("Patient/P"), requestDetails);
// Make sure we're deleted
try {
myPatientDao.read(new IdType("Patient/P"), requestDetails);
fail();
} catch (ResourceGoneException e) {
// good
}
// Search should still work
IBundleProvider searchOutcome = myAuditEventDao.search(SearchParameterMap.newSynchronous(AuditEvent.SP_AGENT, new ReferenceParam("Patient/P")), requestDetails);
assertEquals(1, searchOutcome.size());
}
@Test
public void testWrongPath() {
mySvc.addPath("AuditEvent.identifier");
mySvc.addPath("Patient.agent.who");
myInterceptorRegistry.registerInterceptor(mySvc);
Patient patient = new Patient();
patient.setId("P");
patient.setActive(true);
myPatientDao.update(patient, requestDetails);
AuditEvent audit = new AuditEvent();
audit.setId("A");
audit.addAgent().getWho().setReference("Patient/P");
myAuditEventDao.update(audit, requestDetails);
// Delete should proceed
try {
myPatientDao.delete(new IdType("Patient/P"), requestDetails);
fail();
} catch (ResourceVersionConflictException e) {
// good
}
}
@Test
public void testCombineWithCascadeDeleteInterceptor() {
try {
myInterceptorRegistry.registerInterceptor(myCascadingDeleteInterceptor);
mySvc.addPath("AuditEvent.agent.who");
myInterceptorRegistry.registerInterceptor(mySvc);
Patient patient = new Patient();
patient.setId("P");
patient.setActive(true);
myPatientDao.update(patient, requestDetails);
AuditEvent audit = new AuditEvent();
audit.setId("A");
audit.addAgent().getWho().setReference("Patient/P");
myAuditEventDao.update(audit, requestDetails);
// Delete should proceed
myPatientDao.delete(new IdType("Patient/P"), requestDetails);
} finally {
myInterceptorRegistry.unregisterInterceptor(myCascadingDeleteInterceptor);
}
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,11 +7,14 @@ import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.util.ValueSetTestUtil;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -19,6 +22,7 @@ import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.UriType;
@ -30,6 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@ -38,6 +43,7 @@ import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -56,6 +62,7 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
public void after() {
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
myDaoConfig.setMaximumExpansionSize(new DaoConfig().getMaximumExpansionSize());
myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled());
}
@ -248,6 +255,49 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
}
/**
* See #4305
*/
@Test
public void testDeleteExpungePreExpandedValueSet() {
myDaoConfig.setExpungeEnabled(true);
// Create valueset
ValueSet vs = myValidationSupport.fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/address-use");
assertNotNull(vs);
IIdType id = myValueSetDao.create(vs).getId().toUnqualifiedVersionless();
// Update valueset
vs.setName("Hello");
assertEquals("2", myValueSetDao.update(vs, mySrd).getId().getVersionIdPart());
runInTransaction(()->{
Optional<ResourceTable> resource = myResourceTableDao.findById(id.getIdPartAsLong());
assertTrue(resource.isPresent());
});
// Precalculate
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
logAllValueSets();
// Delete
myValueSetDao.delete(id, mySrd);
// Verify it's deleted
assertThrows(ResourceGoneException.class, ()-> myValueSetDao.read(id, mySrd));
// Expunge
myValueSetDao.expunge(id, new ExpungeOptions().setExpungeDeletedResources(true).setExpungeOldVersions(true), mySrd);
// Verify expunged
runInTransaction(()->{
Optional<ResourceTable> resource = myResourceTableDao.findById(id.getIdPartAsLong());
assertFalse(resource.isPresent());
});
}
@Test
public void testExpandByValueSet_ExceedsMaxSize() {
// Add a bunch of codes

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.util;
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
import ca.uhn.fhir.rest.api.EncodingEnum;

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -53,6 +53,11 @@
<artifactId>hapi-fhir-server-openapi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-ips</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.helger</groupId>

View File

@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.bulk.export.provider.BulkDataExportProvider;
import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
@ -152,6 +153,7 @@ public class TestRestfulServer extends RestfulServer {
setServerConformanceProvider(confProvider);
providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class));
providers.add(myAppCtx.getBean(GraphQLProvider.class));
providers.add(myAppCtx.getBean(IpsOperationProvider.class));
break;
}
case "R4B": {

View File

@ -2,9 +2,15 @@ package ca.uhn.fhirtest.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.config.HapiJpaConfig;
import ca.uhn.fhir.jpa.config.r4.JpaR4Config;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc;
import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@ -196,5 +202,19 @@ public class TestR4Config {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public IIpsGenerationStrategy ipsGenerationStrategy() {
return new DefaultIpsGenerationStrategy();
}
@Bean
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy, theDaoRegistry);
}
@Bean
public IpsOperationProvider ipsOperationProvider(IIpsGeneratorSvc theIpsGeneratorSvc) {
return new IpsOperationProvider(theIpsGeneratorSvc);
}
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -35,7 +35,7 @@ public class NicknameMatcher implements IMdmStringMatcher {
try {
myNicknameSvc = new NicknameSvc();
} catch (IOException e) {
throw new ConfigurationException(Msg.code(2078) + "Unable to load nicknames", e);
throw new ConfigurationException(Msg.code(2234) + "Unable to load nicknames", e);
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -814,7 +814,7 @@ public class ResponseHighlighterInterceptor {
writeLength(theServletResponse, outputBuffer.length());
theServletResponse.getWriter().append(" total including HTML)");
theServletResponse.getWriter().append(" in estimated ");
theServletResponse.getWriter().append(" in approximately ");
theServletResponse.getWriter().append(writeSw.toString());
theServletResponse.getWriter().append("</div>");

View File

@ -173,7 +173,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
if (versions == null || versions.isEmpty()) {
throw new ResourceNotFoundException(Msg.code(1979) + theId);
throw new ResourceNotFoundException(Msg.code(2250) + theId);
}
T deletedInstance = (T) myFhirContext.getResourceDefinition(myResourceType).newInstance();
@ -240,7 +240,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
public synchronized List<IBaseResource> historyInstance(@IdParam IIdType theId, RequestDetails theRequestDetails) {
LinkedList<T> retVal = myIdToHistory.get(theId.getIdPart());
if (retVal == null) {
throw new ResourceNotFoundException(Msg.code(1980) + theId);
throw new ResourceNotFoundException(Msg.code(2248) + theId);
}
return fireInterceptorsAndFilterAsNeeded(retVal, theRequestDetails);
@ -255,7 +255,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
public synchronized T read(@IdParam IIdType theId, RequestDetails theRequestDetails) {
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
if (versions == null || versions.isEmpty()) {
throw new ResourceNotFoundException(Msg.code(1981) + theId);
throw new ResourceNotFoundException(Msg.code(2247) + theId);
}
T retVal;
@ -271,14 +271,14 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
}
if (retVal == null || ResourceMetadataKeyEnum.DELETED_AT.get(retVal) != null) {
throw new ResourceGoneException(Msg.code(1983) + theId);
throw new ResourceGoneException(Msg.code(2244) + theId);
}
myReadCount.incrementAndGet();
retVal = fireInterceptorsAndFilterAsNeeded(retVal, theRequestDetails);
if (retVal == null) {
throw new ResourceNotFoundException(Msg.code(1984) + theId);
throw new ResourceNotFoundException(Msg.code(2243) + theId);
}
return retVal;
}

View File

@ -636,7 +636,7 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
@Read(typeName = "OperationDefinition")
public IBaseResource readOperationDefinition(@IdParam IIdType theId, RequestDetails theRequestDetails) {
if (theId == null || theId.hasIdPart() == false) {
throw new ResourceNotFoundException(Msg.code(1977) + theId);
throw new ResourceNotFoundException(Msg.code(2245) + theId);
}
RestfulServerConfiguration configuration = getServerConfiguration();
Bindings bindings = configuration.provideBindings();
@ -650,7 +650,7 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
if (searchBindings != null && !searchBindings.isEmpty()) {
return readOperationDefinitionForNamedSearch(searchBindings);
}
throw new ResourceNotFoundException(Msg.code(1978) + theId);
throw new ResourceNotFoundException(Msg.code(2249) + theId);
}
private String getOperationId(IIdType theId) {

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -20,7 +20,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -109,7 +109,7 @@ public final class ColumnTypeToDriverTypeToSqlType {
setColumnType(ColumnTypeEnum.CLOB, DriverTypeEnum.MARIADB_10_1, "longtext");
setColumnType(ColumnTypeEnum.CLOB, DriverTypeEnum.MYSQL_5_7, "longtext");
setColumnType(ColumnTypeEnum.CLOB, DriverTypeEnum.ORACLE_12C, "clob");
setColumnType(ColumnTypeEnum.CLOB, DriverTypeEnum.POSTGRES_9_4, "text");
setColumnType(ColumnTypeEnum.CLOB, DriverTypeEnum.POSTGRES_9_4, "oid"); // the PG driver will write oid into a `text` column
setColumnType(ColumnTypeEnum.CLOB, DriverTypeEnum.MSSQL_2012, "varchar(MAX)");
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -122,7 +122,7 @@ public class FetchResourceIdsStep implements IFirstJobStepWorker<BulkExportJobPa
theDataSink.recoveredError(ex.getMessage());
throw new JobExecutionFailedException(Msg.code(2104) + " : " + ex.getMessage());
throw new JobExecutionFailedException(Msg.code(2239) + " : " + ex.getMessage());
}
ourLog.info("Submitted {} groups of ids for processing", submissionCount);

View File

@ -98,7 +98,7 @@ public class WriteBinaryStep implements IJobStepWorker<BulkExportJobParameters,
ex.getMessage());
ourLog.error(errorMsg);
throw new JobExecutionFailedException(Msg.code(2105) + errorMsg);
throw new JobExecutionFailedException(Msg.code(2238) + errorMsg);
}
DaoMethodOutcome outcome = binaryDao.create(binary,

View File

@ -72,7 +72,7 @@ public class Batch2JobRunnerImpl implements IBatch2JobRunner {
public Batch2JobInfo getJobInfo(String theJobId) {
JobInstance instance = myJobCoordinator.getInstance(theJobId);
if (instance == null) {
throw new ResourceNotFoundException(Msg.code(2102) + " : " + theJobId);
throw new ResourceNotFoundException(Msg.code(2240) + " : " + theJobId);
}
return fromJobInstanceToBatch2JobInfo(instance);
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -475,7 +475,7 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState
if (searchBindings != null && !searchBindings.isEmpty()) {
return readOperationDefinitionForNamedSearch(searchBindings);
}
throw new ResourceNotFoundException(Msg.code(1985) + theId);
throw new ResourceNotFoundException(Msg.code(2257) + theId);
}
private OperationDefinition readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.14-SNAPSHOT</version>
<version>6.5.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

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