Merge branch 'master' into 2958-consistent-error-handling

This commit is contained in:
jdar8 2021-09-10 17:02:31 -07:00 committed by GitHub
commit a02a9e0254
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
137 changed files with 2723 additions and 616 deletions

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -961,6 +961,7 @@ public class FhirTerser {
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) { for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
List<?> values = nextChild.getAccessor().getValues(theElement); List<?> values = nextChild.getAccessor().getValues(theElement);
if (values != null) { if (values != null) {
for (Object nextValueObject : values) { for (Object nextValueObject : values) {
IBase nextValue; IBase nextValue;

View File

@ -3,14 +3,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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -282,7 +282,7 @@ public abstract class BaseApp {
} }
private Optional<BaseCommand> parseCommand(String[] theArgs) { private Optional<BaseCommand> parseCommand(String[] theArgs) {
Optional<BaseCommand> commandOpt = getNextCommand(theArgs); Optional<BaseCommand> commandOpt = getNextCommand(theArgs, 0);
if (! commandOpt.isPresent()) { if (! commandOpt.isPresent()) {
String message = "Unrecognized command: " + ansi().bold().fg(Ansi.Color.RED) + theArgs[0] + ansi().boldOff().fg(Ansi.Color.WHITE); String message = "Unrecognized command: " + ansi().bold().fg(Ansi.Color.RED) + theArgs[0] + ansi().boldOff().fg(Ansi.Color.WHITE);
@ -294,8 +294,8 @@ public abstract class BaseApp {
return commandOpt; return commandOpt;
} }
private Optional<BaseCommand> getNextCommand(String[] theArgs) { private Optional<BaseCommand> getNextCommand(String[] theArgs, int thePosition) {
return ourCommands.stream().filter(cmd -> cmd.getCommandName().equals(theArgs[0])).findFirst(); return ourCommands.stream().filter(cmd -> cmd.getCommandName().equals(theArgs[thePosition])).findFirst();
} }
private void processHelp(String[] theArgs) { private void processHelp(String[] theArgs) {
@ -303,7 +303,7 @@ public abstract class BaseApp {
logUsage(); logUsage();
return; return;
} }
Optional<BaseCommand> commandOpt = getNextCommand(theArgs); Optional<BaseCommand> commandOpt = getNextCommand(theArgs, 1);
if (! commandOpt.isPresent()) { if (! commandOpt.isPresent()) {
String message = "Unknown command: " + theArgs[1]; String message = "Unknown command: " + theArgs[1];
System.err.println(message); System.err.println(message);

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.cli;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
public class BaseAppTest {
private final PrintStream standardOut = System.out;
private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
@BeforeEach
public void setUp() {
System.setOut(new PrintStream(outputStreamCaptor));
}
@AfterEach
public void tearDown() {
System.setOut(standardOut);
}
@Test
public void testHelpOption() {
App.main(new String[]{"help", "create-package"});
assertThat(outputStreamCaptor.toString().trim(), outputStreamCaptor.toString().trim(), containsString("Usage"));
}
}

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath> <relativePath>../../hapi-deployable-pom</relativePath>
</parent> </parent>

View File

@ -30,6 +30,7 @@ import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -65,8 +66,8 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("HAPI_PU"); retVal.setPersistenceUnitName("HAPI_PU");
retVal.setDataSource(myDataSource); retVal.setDataSource(myDataSource);
retVal.setJpaProperties(myJpaProperties); retVal.setJpaProperties(myJpaProperties);

View File

@ -30,6 +30,7 @@ import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -62,8 +63,8 @@ public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("HAPI_PU"); retVal.setPersistenceUnitName("HAPI_PU");
retVal.setDataSource(myDataSource); retVal.setDataSource(myDataSource);
retVal.setJpaProperties(myJpaProperties); retVal.setJpaProperties(myJpaProperties);

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -60,8 +61,8 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("HAPI_PU"); retVal.setPersistenceUnitName("HAPI_PU");
retVal.setDataSource(myDataSource); retVal.setDataSource(myDataSource);
retVal.setJpaProperties(myJpaProperties); retVal.setJpaProperties(myJpaProperties);

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2901
jira: SMILE-3004
title: "Processing transactions with AutoversionAtPaths set should create those resources (if AutoCreatePlaceholders is set) and use latest version as expected"

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2962
jira: SMILE-720
title: "Added a new DaoConfig setting called `setElasticSearchIndexPrefix(String prefix)` which will cause Hibernate search to prefix all of its tables with the provided value."

View File

@ -0,0 +1,7 @@
---
type: fix
issue: 2967
jira: SMILE-2899
title: "Previously, the system would only traverse references to discrete resources while performing a chained search.
This fix adds support for traversing references to contained resources as well, with the limitation that the reference
to the contained resource must be the last reference in the chain."

View File

@ -0,0 +1,4 @@
---
type: fix
issue: 2973
title: "CLI `smileutil help {command}` returns `Unknown command` which should return the usage of `command`. This has been corrected."

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2975
title: "Two improvements have been made to the connection to Elasticsearch. First, null username and password values are now permitted. Second, multiple hosts are now permitted via the `setHosts()` method on the ElasticHibernatePropertiesBuilder, allowing you to
connect to multiple elasticsearch clusters at once. Thanks to Dušan Marković for the contribution!"

View File

@ -0,0 +1,3 @@
---
type: fix
title: "Fixed a bug where two identical tags in parallel entries being created in a batch would fail."

View File

@ -31,12 +31,11 @@ In addition, the Elasticsearch client service, `ElasticsearchSvcImpl` will need
```java ```java
@Bean() @Bean()
public ElasticsearchSvcImpl elasticsearchSvc() { public ElasticsearchSvcImpl elasticsearchSvc() {
String elasticsearchHost = "localhost"; String elasticsearchHost = "localhost:9200";
String elasticsearchUserId = "elastic"; String elasticsearchUsername = "elastic";
String elasticsearchPassword = "changeme"; String elasticsearchPassword = "changeme";
int elasticsearchPort = 9301;
return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUserId, elasticsearchPassword); return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchUsername, elasticsearchPassword);
} }
``` ```

View File

@ -302,6 +302,14 @@ If the server has been configured with a [Resource Server ID Strategy](/apidocs/
Contains the specific version (starting with 1) of the resource that this row corresponds to. Contains the specific version (starting with 1) of the resource that this row corresponds to.
</td> </td>
</tr> </tr>
<tr>
<td>RESOURCE_TYPE</td>
<td></td>
<td>String</td>
<td>
Contains the string specifying the type of the resource (Patient, Observation, etc).
</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -476,7 +484,7 @@ The following columns are common to **all HFJ_SPIDX_xxx tables**.
<tr> <tr>
<td>RES_ID</td> <td>RES_ID</td>
<td>FK to <a href="#HFJ_RESOURCE">HFJ_RESOURCE</a></td> <td>FK to <a href="#HFJ_RESOURCE">HFJ_RESOURCE</a></td>
<td>String</td> <td>Long</td>
<td></td> <td></td>
<td> <td>
Contains the PID of the resource being indexed. Contains the PID of the resource being indexed.

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -258,6 +258,11 @@ public class DaoConfig {
private boolean myAccountForDateIndexNulls; private boolean myAccountForDateIndexNulls;
private boolean myTriggerSubscriptionsForNonVersioningChanges; private boolean myTriggerSubscriptionsForNonVersioningChanges;
/**
* @since 5.6.0
*/
private String myElasicSearchIndexPrefix;
/** /**
* @since 5.6.0 * @since 5.6.0
*/ */
@ -269,6 +274,7 @@ public class DaoConfig {
private Integer myBundleBatchPoolSize = DEFAULT_BUNDLE_BATCH_POOL_SIZE; private Integer myBundleBatchPoolSize = DEFAULT_BUNDLE_BATCH_POOL_SIZE;
private Integer myBundleBatchMaxPoolSize = DEFAULT_BUNDLE_BATCH_MAX_POOL_SIZE; private Integer myBundleBatchMaxPoolSize = DEFAULT_BUNDLE_BATCH_MAX_POOL_SIZE;
/** /**
* Constructor * Constructor
*/ */
@ -2643,6 +2649,28 @@ public class DaoConfig {
return retval; return retval;
} }
/**
*
* Sets a prefix for any indexes created when interacting with elasticsearch. This will apply to fulltext search indexes
* and terminology expansion indexes.
*
* @since 5.6.0
*/
public String getElasticSearchIndexPrefix() {
return myElasicSearchIndexPrefix;
}
/**
*
* Sets a prefix for any indexes created when interacting with elasticsearch. This will apply to fulltext search indexes
* and terminology expansion indexes.
*
* @since 5.6.0
*/
public void setElasticSearchIndexPrefix(String thePrefix) {
myElasicSearchIndexPrefix = thePrefix;
}
public enum StoreMetaSourceInformationEnum { public enum StoreMetaSourceInformationEnum {
NONE(false, false), NONE(false, false),
SOURCE_URI(true, false), SOURCE_URI(true, false),

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.api.dao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
import ca.uhn.fhir.rest.annotation.Offset;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -24,18 +24,23 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 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.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails; import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.QueryChunker; import ca.uhn.fhir.jpa.util.QueryChunker;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.slf4j.LoggerFactory.getLogger; import static org.slf4j.LoggerFactory.getLogger;
@ -52,17 +57,19 @@ public class ResourceVersionSvcDaoImpl implements IResourceVersionSvc {
DaoRegistry myDaoRegistry; DaoRegistry myDaoRegistry;
@Autowired @Autowired
IResourceTableDao myResourceTableDao; IResourceTableDao myResourceTableDao;
@Autowired
IdHelperService myIdHelperService;
@Override @Override
@Nonnull @Nonnull
public ResourceVersionMap getVersionMap(String theResourceName, SearchParameterMap theSearchParamMap) { public ResourceVersionMap getVersionMap(RequestPartitionId theRequestPartitionId, String theResourceName, SearchParameterMap theSearchParamMap) {
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceName); IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceName);
if (ourLog.isDebugEnabled()) { if (ourLog.isDebugEnabled()) {
ourLog.debug("About to retrieve version map for resource type: {}", theResourceName); ourLog.debug("About to retrieve version map for resource type: {}", theResourceName);
} }
List<Long> matchingIds = dao.searchForIds(theSearchParamMap, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.allPartitions())).stream() List<Long> matchingIds = dao.searchForIds(theSearchParamMap, new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId)).stream()
.map(ResourcePersistentId::getIdAsLong) .map(ResourcePersistentId::getIdAsLong)
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -74,4 +81,95 @@ public class ResourceVersionSvcDaoImpl implements IResourceVersionSvc {
return ResourceVersionMap.fromResourceTableEntities(allById); return ResourceVersionMap.fromResourceTableEntities(allById);
} }
@Override
/**
* Retrieves the latest versions for any resourceid that are found.
* If they are not found, they will not be contained in the returned map.
* The key should be the same value that was passed in to allow
* consumer to look up the value using the id they already have.
*
* This method should not throw, so it can safely be consumed in
* transactions.
*
* @param theRequestPartitionId - request partition id
* @param theIds - list of IIdTypes for resources of interest.
* @return
*/
public ResourcePersistentIdMap getLatestVersionIdsForResourceIds(RequestPartitionId theRequestPartitionId, List<IIdType> theIds) {
ResourcePersistentIdMap idToPID = new ResourcePersistentIdMap();
HashMap<String, List<IIdType>> resourceTypeToIds = new HashMap<>();
for (IIdType id : theIds) {
String resourceType = id.getResourceType();
if (!resourceTypeToIds.containsKey(resourceType)) {
resourceTypeToIds.put(resourceType, new ArrayList<>());
}
resourceTypeToIds.get(resourceType).add(id);
}
for (String resourceType : resourceTypeToIds.keySet()) {
ResourcePersistentIdMap idAndPID = getIdsOfExistingResources(theRequestPartitionId,
resourceTypeToIds.get(resourceType));
idToPID.putAll(idAndPID);
}
return idToPID;
}
/**
* Helper method to determine if some resources exist in the DB (without throwing).
* Returns a set that contains the IIdType for every resource found.
* If it's not found, it won't be included in the set.
*
* @param theIds - list of IIdType ids (for the same resource)
* @return
*/
private ResourcePersistentIdMap getIdsOfExistingResources(RequestPartitionId thePartitionId,
Collection<IIdType> theIds) {
// these are the found Ids that were in the db
ResourcePersistentIdMap retval = new ResourcePersistentIdMap();
if (theIds == null || theIds.isEmpty()) {
return retval;
}
List<ResourcePersistentId> resourcePersistentIds = myIdHelperService.resolveResourcePersistentIdsWithCache(thePartitionId,
theIds.stream().collect(Collectors.toList()));
// we'll use this map to fetch pids that require versions
HashMap<Long, ResourcePersistentId> pidsToVersionToResourcePid = new HashMap<>();
// fill in our map
for (ResourcePersistentId pid : resourcePersistentIds) {
if (pid.getVersion() == null) {
pidsToVersionToResourcePid.put(pid.getIdAsLong(), pid);
}
Optional<IIdType> idOp = theIds.stream()
.filter(i -> i.getIdPart().equals(pid.getAssociatedResourceId().getIdPart()))
.findFirst();
// this should always be present
// since it was passed in.
// but land of optionals...
idOp.ifPresent(id -> {
retval.put(id, pid);
});
}
// set any versions we don't already have
if (!pidsToVersionToResourcePid.isEmpty()) {
Collection<Object[]> resourceEntries = myResourceTableDao
.getResourceVersionsForPid(new ArrayList<>(pidsToVersionToResourcePid.keySet()));
for (Object[] record : resourceEntries) {
// order matters!
Long retPid = (Long) record[0];
String resType = (String) record[1];
Long version = (Long) record[2];
pidsToVersionToResourcePid.get(retPid).setVersion(version);
}
}
return retval;
}
} }

View File

@ -122,6 +122,7 @@ import ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl;
import ca.uhn.fhir.jpa.search.cache.DatabaseSearchResultCacheSvcImpl; import ca.uhn.fhir.jpa.search.cache.DatabaseSearchResultCacheSvcImpl;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
import ca.uhn.fhir.jpa.search.elastic.IndexNamePrefixLayoutStrategy;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexer; import ca.uhn.fhir.jpa.search.reindex.ResourceReindexer;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl; import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
@ -155,6 +156,7 @@ import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.springframework.batch.core.configuration.annotation.BatchConfigurer; import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -288,8 +290,8 @@ public abstract class BaseConfig {
* bean, but it provides a partially completed entity manager * bean, but it provides a partially completed entity manager
* factory with HAPI FHIR customizations * factory with HAPI FHIR customizations
*/ */
protected LocalContainerEntityManagerFactoryBean entityManagerFactory() { protected LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory myConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = new HapiFhirLocalContainerEntityManagerFactoryBean(); LocalContainerEntityManagerFactoryBean retVal = new HapiFhirLocalContainerEntityManagerFactoryBean(myConfigurableListableBeanFactory);
configureEntityManagerFactory(retVal, fhirContext()); configureEntityManagerFactory(retVal, fhirContext());
return retVal; return retVal;
} }
@ -919,6 +921,11 @@ public abstract class BaseConfig {
return new PredicateBuilderFactory(theApplicationContext); return new PredicateBuilderFactory(theApplicationContext);
} }
@Bean
public IndexNamePrefixLayoutStrategy indexLayoutStrategy() {
return new IndexNamePrefixLayoutStrategy();
}
@Bean @Bean
public JpaResourceLoader jpaResourceLoader() { public JpaResourceLoader jpaResourceLoader() {
return new JpaResourceLoader(); return new JpaResourceLoader();

View File

@ -23,6 +23,9 @@ package ca.uhn.fhir.jpa.config;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.orm.hibernate5.SpringBeanContainer;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import java.util.Map; import java.util.Map;
@ -32,6 +35,14 @@ import java.util.Map;
* that sets some sensible default property values * that sets some sensible default property values
*/ */
public class HapiFhirLocalContainerEntityManagerFactoryBean extends LocalContainerEntityManagerFactoryBean { public class HapiFhirLocalContainerEntityManagerFactoryBean extends LocalContainerEntityManagerFactoryBean {
//https://stackoverflow.com/questions/57902388/how-to-inject-spring-beans-into-the-hibernate-envers-revisionlistener
ConfigurableListableBeanFactory myConfigurableListableBeanFactory;
public HapiFhirLocalContainerEntityManagerFactoryBean(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
myConfigurableListableBeanFactory = theConfigurableListableBeanFactory;
}
@Override @Override
public Map<String, Object> getJpaPropertyMap() { public Map<String, Object> getJpaPropertyMap() {
Map<String, Object> retVal = super.getJpaPropertyMap(); Map<String, Object> retVal = super.getJpaPropertyMap();
@ -63,6 +74,11 @@ public class HapiFhirLocalContainerEntityManagerFactoryBean extends LocalContain
if (!retVal.containsKey(AvailableSettings.BATCH_VERSIONED_DATA)) { if (!retVal.containsKey(AvailableSettings.BATCH_VERSIONED_DATA)) {
retVal.put(AvailableSettings.BATCH_VERSIONED_DATA, "true"); retVal.put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
} }
// Why is this here, you ask? LocalContainerEntityManagerFactoryBean actually clobbers the setting hibernate needs
// in order to be able to resolve beans, so we add it back in manually here
if (!retVal.containsKey(AvailableSettings.BEAN_CONTAINER)) {
retVal.put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(myConfigurableListableBeanFactory));
}
return retVal; return retVal;
} }

View File

@ -1207,7 +1207,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (thePerformIndexing || ((ResourceTable) theEntity).getVersion() == 1) { if (thePerformIndexing || ((ResourceTable) theEntity).getVersion() == 1) {
newParams = new ResourceIndexedSearchParams(); newParams = new ResourceIndexedSearchParams();
mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing); mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing);
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true); changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);

View File

@ -136,9 +136,12 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;

View File

@ -25,19 +25,18 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
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.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.api.model.LazyDaoMethodOutcome; import ca.uhn.fhir.jpa.api.model.LazyDaoMethodOutcome;
import ca.uhn.fhir.jpa.cache.IResourceVersionSvc;
import ca.uhn.fhir.jpa.cache.ResourcePersistentIdMap;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.rest.api.QualifiedParamList; import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails;
@ -45,12 +44,16 @@ import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails; import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails; import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.QualifierDetails; import ca.uhn.fhir.rest.param.QualifierDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
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.ISearchParamRegistry;
import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.OperationOutcomeUtil;
@ -91,6 +94,10 @@ public abstract class BaseStorageDao {
protected DaoRegistry myDaoRegistry; protected DaoRegistry myDaoRegistry;
@Autowired @Autowired
protected ModelConfig myModelConfig; protected ModelConfig myModelConfig;
@Autowired
protected IResourceVersionSvc myResourceVersionSvc;
@Autowired
protected DaoConfig myDaoConfig;
@VisibleForTesting @VisibleForTesting
public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) { public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
@ -204,10 +211,33 @@ public abstract class BaseStorageDao {
for (IBaseReference nextReference : referencesToVersion) { for (IBaseReference nextReference : referencesToVersion) {
IIdType referenceElement = nextReference.getReferenceElement(); IIdType referenceElement = nextReference.getReferenceElement();
if (!referenceElement.hasBaseUrl()) { if (!referenceElement.hasBaseUrl()) {
String resourceType = referenceElement.getResourceType();
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType); ResourcePersistentIdMap resourceVersionMap = myResourceVersionSvc.getLatestVersionIdsForResourceIds(RequestPartitionId.allPartitions(),
String targetVersionId = dao.getCurrentVersionId(referenceElement); Collections.singletonList(referenceElement)
String newTargetReference = referenceElement.withVersion(targetVersionId).getValue(); );
// 3 cases:
// 1) there exists a resource in the db with some version (use this version)
// 2) no resource exists, but we will create one (eventually). The version is 1
// 3) no resource exists, and none will be made -> throw
Long version;
if (resourceVersionMap.containsKey(referenceElement)) {
// the resource exists... latest id
// will be the value in the ResourcePersistentId
version = resourceVersionMap.getResourcePersistentId(referenceElement).getVersion();
} else if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
// if idToPID doesn't contain object
// but autcreateplaceholders is on
// then the version will be 1 (the first version)
version = 1L;
}
else {
// resource not found
// and no autocreateplaceholders set...
// we throw
throw new ResourceNotFoundException(referenceElement);
}
String newTargetReference = referenceElement.withVersion(version.toString()).getValue();
nextReference.setReference(newTargetReference); nextReference.setReference(newTargetReference);
} }
} }

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.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.TransactionWriteOperationsDetails; import ca.uhn.fhir.interceptor.model.TransactionWriteOperationsDetails;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
@ -34,6 +35,8 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.api.model.DeleteConflict; import ca.uhn.fhir.jpa.api.model.DeleteConflict;
import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
import ca.uhn.fhir.jpa.cache.IResourceVersionSvc;
import ca.uhn.fhir.jpa.cache.ResourcePersistentIdMap;
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.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
@ -68,11 +71,11 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.rest.server.util.ServletRequestUtil; import ca.uhn.fhir.rest.server.util.ServletRequestUtil;
import ca.uhn.fhir.util.AsyncUtil;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ResourceReferenceInfo; import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.AsyncUtil;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
@ -90,7 +93,6 @@ import org.hl7.fhir.instance.model.api.IBaseReference;
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 org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Task;
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;
@ -117,11 +119,11 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.StringUtil.toUtf8String; import static ca.uhn.fhir.util.StringUtil.toUtf8String;
import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.defaultString;
@ -157,6 +159,9 @@ public abstract class BaseTransactionProcessor {
private TaskExecutor myExecutor ; private TaskExecutor myExecutor ;
@Autowired
private IResourceVersionSvc myResourceVersionSvc;
@VisibleForTesting @VisibleForTesting
public void setDaoConfig(DaoConfig theDaoConfig) { public void setDaoConfig(DaoConfig theDaoConfig) {
myDaoConfig = theDaoConfig; myDaoConfig = theDaoConfig;
@ -252,8 +257,10 @@ public abstract class BaseTransactionProcessor {
myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry); myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry);
} }
private void handleTransactionCreateOrUpdateOutcome(Map<IIdType, IIdType> idSubstitutions, Map<IIdType, DaoMethodOutcome> idToPersistedOutcome, IIdType nextResourceId, DaoMethodOutcome outcome, private void handleTransactionCreateOrUpdateOutcome(Map<IIdType, IIdType> idSubstitutions, Map<IIdType, DaoMethodOutcome> idToPersistedOutcome,
IBase newEntry, String theResourceType, IBaseResource theRes, RequestDetails theRequestDetails) { IIdType nextResourceId, DaoMethodOutcome outcome,
IBase newEntry, String theResourceType,
IBaseResource theRes, RequestDetails theRequestDetails) {
IIdType newId = outcome.getId().toUnqualified(); IIdType newId = outcome.getId().toUnqualified();
IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless(); IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
if (newId.equals(resourceId) == false) { if (newId.equals(resourceId) == false) {
@ -359,8 +366,8 @@ public abstract class BaseTransactionProcessor {
IBase nextRequestEntry = null; IBase nextRequestEntry = null;
for (int i=0; i<requestEntriesSize; i++ ) { for (int i=0; i<requestEntriesSize; i++ ) {
nextRequestEntry = requestEntries.get(i); nextRequestEntry = requestEntries.get(i);
BundleTask bundleTask = new BundleTask(completionLatch, theRequestDetails, responseMap, i, nextRequestEntry, theNestedMode); RetriableBundleTask retriableBundleTask = new RetriableBundleTask(completionLatch, theRequestDetails, responseMap, i, nextRequestEntry, theNestedMode);
getTaskExecutor().execute(bundleTask); getTaskExecutor().execute(retriableBundleTask);
} }
// waiting for all tasks to be completed // waiting for all tasks to be completed
@ -394,7 +401,8 @@ public abstract class BaseTransactionProcessor {
myHapiTransactionService = theHapiTransactionService; myHapiTransactionService = theHapiTransactionService;
} }
private IBaseBundle processTransaction(final RequestDetails theRequestDetails, final IBaseBundle theRequest, final String theActionName, boolean theNestedMode) { private IBaseBundle processTransaction(final RequestDetails theRequestDetails, final IBaseBundle theRequest,
final String theActionName, boolean theNestedMode) {
validateDependencies(); validateDependencies();
String transactionType = myVersionAdapter.getBundleType(theRequest); String transactionType = myVersionAdapter.getBundleType(theRequest);
@ -412,7 +420,8 @@ public abstract class BaseTransactionProcessor {
throw new InvalidRequestException("Unable to process transaction where incoming Bundle.type = " + transactionType); throw new InvalidRequestException("Unable to process transaction where incoming Bundle.type = " + transactionType);
} }
int numberOfEntries = myVersionAdapter.getEntries(theRequest).size(); List<IBase> requestEntries = myVersionAdapter.getEntries(theRequest);
int numberOfEntries = requestEntries.size();
if (myDaoConfig.getMaximumTransactionBundleSize() != null && numberOfEntries > myDaoConfig.getMaximumTransactionBundleSize()) { if (myDaoConfig.getMaximumTransactionBundleSize() != null && numberOfEntries > myDaoConfig.getMaximumTransactionBundleSize()) {
throw new PayloadTooLargeException("Transaction Bundle Too large. Transaction bundle contains " + throw new PayloadTooLargeException("Transaction Bundle Too large. Transaction bundle contains " +
@ -425,8 +434,6 @@ public abstract class BaseTransactionProcessor {
final TransactionDetails transactionDetails = new TransactionDetails(); final TransactionDetails transactionDetails = new TransactionDetails();
final StopWatch transactionStopWatch = new StopWatch(); final StopWatch transactionStopWatch = new StopWatch();
List<IBase> requestEntries = myVersionAdapter.getEntries(theRequest);
// Do all entries have a verb? // Do all entries have a verb?
for (int i = 0; i < numberOfEntries; i++) { for (int i = 0; i < numberOfEntries; i++) {
IBase nextReqEntry = requestEntries.get(i); IBase nextReqEntry = requestEntries.get(i);
@ -450,10 +457,11 @@ public abstract class BaseTransactionProcessor {
List<IBase> getEntries = new ArrayList<>(); List<IBase> getEntries = new ArrayList<>();
final IdentityHashMap<IBase, Integer> originalRequestOrder = new IdentityHashMap<>(); final IdentityHashMap<IBase, Integer> originalRequestOrder = new IdentityHashMap<>();
for (int i = 0; i < requestEntries.size(); i++) { for (int i = 0; i < requestEntries.size(); i++) {
originalRequestOrder.put(requestEntries.get(i), i); IBase requestEntry = requestEntries.get(i);
originalRequestOrder.put(requestEntry, i);
myVersionAdapter.addEntry(response); myVersionAdapter.addEntry(response);
if (myVersionAdapter.getEntryRequestVerb(myContext, requestEntries.get(i)).equals("GET")) { if (myVersionAdapter.getEntryRequestVerb(myContext, requestEntry).equals("GET")) {
getEntries.add(requestEntries.get(i)); getEntries.add(requestEntry);
} }
} }
@ -472,16 +480,43 @@ public abstract class BaseTransactionProcessor {
} }
entries.sort(new TransactionSorter(placeholderIds)); entries.sort(new TransactionSorter(placeholderIds));
doTransactionWriteOperations(theRequestDetails, theActionName, transactionDetails, transactionStopWatch, response, originalRequestOrder, entries); // perform all writes
doTransactionWriteOperations(theRequestDetails, theActionName,
transactionDetails, transactionStopWatch,
response, originalRequestOrder, entries);
// perform all gets
// (we do these last so that the gets happen on the final state of the DB;
// see above note)
doTransactionReadOperations(theRequestDetails, response,
getEntries, originalRequestOrder,
transactionStopWatch, theNestedMode);
// Interceptor broadcast: JPA_PERFTRACE_INFO
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequestDetails)) {
String taskDurations = transactionStopWatch.formatTaskDurations();
StorageProcessingMessage message = new StorageProcessingMessage();
message.setMessage("Transaction timing:\n" + taskDurations);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(StorageProcessingMessage.class, message);
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_INFO, params);
}
return response;
}
private void doTransactionReadOperations(final RequestDetails theRequestDetails, IBaseBundle theResponse,
List<IBase> theGetEntries, IdentityHashMap<IBase, Integer> theOriginalRequestOrder,
StopWatch theTransactionStopWatch, boolean theNestedMode) {
if (theGetEntries.size() > 0) {
theTransactionStopWatch.startTask("Process " + theGetEntries.size() + " GET entries");
/* /*
* Loop through the request and process any entries of type GET * Loop through the request and process any entries of type GET
*/ */
if (getEntries.size() > 0) { for (IBase nextReqEntry : theGetEntries) {
transactionStopWatch.startTask("Process " + getEntries.size() + " GET entries");
}
for (IBase nextReqEntry : getEntries) {
if (theNestedMode) { if (theNestedMode) {
throw new InvalidRequestException("Can not invoke read operation on nested transaction"); throw new InvalidRequestException("Can not invoke read operation on nested transaction");
} }
@ -491,8 +526,8 @@ public abstract class BaseTransactionProcessor {
} }
ServletRequestDetails srd = (ServletRequestDetails) theRequestDetails; ServletRequestDetails srd = (ServletRequestDetails) theRequestDetails;
Integer originalOrder = originalRequestOrder.get(nextReqEntry); Integer originalOrder = theOriginalRequestOrder.get(nextReqEntry);
IBase nextRespEntry = (IBase) myVersionAdapter.getEntries(response).get(originalOrder); IBase nextRespEntry = (IBase) myVersionAdapter.getEntries(theResponse).get(originalOrder);
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create(); ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
@ -519,7 +554,6 @@ public abstract class BaseTransactionProcessor {
Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {}", url); Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {}", url);
try { try {
BaseResourceReturningMethodBinding methodBinding = (BaseResourceReturningMethodBinding) method; BaseResourceReturningMethodBinding methodBinding = (BaseResourceReturningMethodBinding) method;
requestDetails.setRestOperationType(methodBinding.getRestOperationType()); requestDetails.setRestOperationType(methodBinding.getRestOperationType());
@ -536,23 +570,9 @@ public abstract class BaseTransactionProcessor {
myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(e.getStatusCode())); myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(e.getStatusCode()));
populateEntryWithOperationOutcome(e, nextRespEntry); populateEntryWithOperationOutcome(e, nextRespEntry);
} }
} }
transactionStopWatch.endCurrentTask(); theTransactionStopWatch.endCurrentTask();
// Interceptor broadcast: JPA_PERFTRACE_INFO
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequestDetails)) {
String taskDurations = transactionStopWatch.formatTaskDurations();
StorageProcessingMessage message = new StorageProcessingMessage();
message.setMessage("Transaction timing:\n" + taskDurations);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(StorageProcessingMessage.class, message);
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_INFO, params);
} }
return response;
} }
/** /**
@ -564,7 +584,10 @@ public abstract class BaseTransactionProcessor {
* heavy load with lots of concurrent transactions using all available * heavy load with lots of concurrent transactions using all available
* database connections. * database connections.
*/ */
private void doTransactionWriteOperations(RequestDetails theRequestDetails, String theActionName, TransactionDetails theTransactionDetails, StopWatch theTransactionStopWatch, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries) { private void doTransactionWriteOperations(RequestDetails theRequestDetails, String theActionName,
TransactionDetails theTransactionDetails, StopWatch theTransactionStopWatch,
IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder,
List<IBase> theEntries) {
TransactionWriteOperationsDetails writeOperationsDetails = null; TransactionWriteOperationsDetails writeOperationsDetails = null;
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_TRANSACTION_WRITE_OPERATIONS_PRE, myInterceptorBroadcaster, theRequestDetails) || if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_TRANSACTION_WRITE_OPERATIONS_PRE, myInterceptorBroadcaster, theRequestDetails) ||
CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_TRANSACTION_WRITE_OPERATIONS_POST, myInterceptorBroadcaster, theRequestDetails)) { CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_TRANSACTION_WRITE_OPERATIONS_POST, myInterceptorBroadcaster, theRequestDetails)) {
@ -593,14 +616,18 @@ public abstract class BaseTransactionProcessor {
.add(TransactionDetails.class, theTransactionDetails) .add(TransactionDetails.class, theTransactionDetails)
.add(TransactionWriteOperationsDetails.class, writeOperationsDetails); .add(TransactionWriteOperationsDetails.class, writeOperationsDetails);
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_TRANSACTION_WRITE_OPERATIONS_PRE, params); CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_TRANSACTION_WRITE_OPERATIONS_PRE, params);
} }
TransactionCallback<Map<IBase, IIdType>> txCallback = status -> { TransactionCallback<Map<IBase, IIdType>> txCallback = status -> {
final Set<IIdType> allIds = new LinkedHashSet<>(); final Set<IIdType> allIds = new LinkedHashSet<>();
final Map<IIdType, IIdType> idSubstitutions = new HashMap<>(); final Map<IIdType, IIdType> idSubstitutions = new HashMap<>();
final Map<IIdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<>(); final Map<IIdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<>();
Map<IBase, IIdType> retVal = doTransactionWriteOperations(theRequestDetails, theActionName, theTransactionDetails, allIds, idSubstitutions, idToPersistedOutcome, theResponse, theOriginalRequestOrder, theEntries, theTransactionStopWatch);
Map<IBase, IIdType> retVal = doTransactionWriteOperations(theRequestDetails, theActionName,
theTransactionDetails, allIds,
idSubstitutions, idToPersistedOutcome,
theResponse, theOriginalRequestOrder,
theEntries, theTransactionStopWatch);
theTransactionStopWatch.startTask("Commit writes to database"); theTransactionStopWatch.startTask("Commit writes to database");
return retVal; return retVal;
@ -609,7 +636,8 @@ public abstract class BaseTransactionProcessor {
try { try {
entriesToProcess = myHapiTransactionService.execute(theRequestDetails, theTransactionDetails, txCallback); entriesToProcess = myHapiTransactionService.execute(theRequestDetails, theTransactionDetails, txCallback);
} finally { }
finally {
if (writeOperationsDetails != null) { if (writeOperationsDetails != null) {
HookParams params = new HookParams() HookParams params = new HookParams()
.add(TransactionDetails.class, theTransactionDetails) .add(TransactionDetails.class, theTransactionDetails)
@ -664,27 +692,12 @@ public abstract class BaseTransactionProcessor {
myModelConfig = theModelConfig; myModelConfig = theModelConfig;
} }
protected Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds, /**
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) { * Searches for duplicate conditional creates and consolidates them.
*
theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts( * @param theEntries
Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED,
Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED,
Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED
);
try {
Set<String> deletedResources = new HashSet<>();
DeleteConflictList deleteConflicts = new DeleteConflictList();
Map<IBase, IIdType> entriesToProcess = new IdentityHashMap<>();
Set<IIdType> nonUpdatedEntities = new HashSet<>();
Set<IBasePersistedResource> updatedEntities = new HashSet<>();
List<IBaseResource> updatedResources = new ArrayList<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
/*
* Look for duplicate conditional creates and consolidate them
*/ */
private void consolidateDuplicateConditionals(List<IBase> theEntries) {
final HashMap<String, String> keyToUuid = new HashMap<>(); final HashMap<String, String> keyToUuid = new HashMap<>();
for (int index = 0, originalIndex = 0; index < theEntries.size(); index++, originalIndex++) { for (int index = 0, originalIndex = 0; index < theEntries.size(); index++, originalIndex++) {
IBase nextReqEntry = theEntries.get(index); IBase nextReqEntry = theEntries.get(index);
@ -743,26 +756,23 @@ public abstract class BaseTransactionProcessor {
} }
} }
} }
/*
* Loop through the request and process any entries of type
* PUT, POST or DELETE
*/
for (int i = 0; i < theEntries.size(); i++) {
if (i % 250 == 0) {
ourLog.debug("Processed {} non-GET entries out of {} in transaction", i, theEntries.size());
} }
IBase nextReqEntry = theEntries.get(i); /**
IBaseResource res = myVersionAdapter.getResource(nextReqEntry); * Retrieves the next resource id (IIdType) from the base resource and next request entry.
* @param theBaseResource - base resource
* @param theNextReqEntry - next request entry
* @param theAllIds - set of all IIdType values
* @return
*/
private IIdType getNextResourceIdFromBaseResource(IBaseResource theBaseResource,
IBase theNextReqEntry,
Set<IIdType> theAllIds) {
IIdType nextResourceId = null; IIdType nextResourceId = null;
if (res != null) { if (theBaseResource != null) {
nextResourceId = theBaseResource.getIdElement();
nextResourceId = res.getIdElement(); String fullUrl = myVersionAdapter.getFullUrl(theNextReqEntry);
String fullUrl = myVersionAdapter.getFullUrl(nextReqEntry);
if (isNotBlank(fullUrl)) { if (isNotBlank(fullUrl)) {
IIdType fullUrlIdType = newIdType(fullUrl); IIdType fullUrlIdType = newIdType(fullUrl);
if (isPlaceholder(fullUrlIdType)) { if (isPlaceholder(fullUrlIdType)) {
@ -777,8 +787,8 @@ public abstract class BaseTransactionProcessor {
} }
if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) { if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) {
nextResourceId = newIdType(toResourceName(res.getClass()), nextResourceId.getIdPart()); nextResourceId = newIdType(toResourceName(theBaseResource.getClass()), nextResourceId.getIdPart());
res.setId(nextResourceId); theBaseResource.setId(nextResourceId);
} }
/* /*
@ -797,6 +807,47 @@ public abstract class BaseTransactionProcessor {
} }
return nextResourceId;
}
protected Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName,
TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder,
List<IBase> theEntries, StopWatch theTransactionStopWatch) {
theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts(
Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED,
Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED,
Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED
);
try {
Set<String> deletedResources = new HashSet<>();
DeleteConflictList deleteConflicts = new DeleteConflictList();
Map<IBase, IIdType> entriesToProcess = new IdentityHashMap<>();
Set<IIdType> nonUpdatedEntities = new HashSet<>();
Set<IBasePersistedResource> updatedEntities = new HashSet<>();
List<IBaseResource> updatedResources = new ArrayList<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
/*
* Look for duplicate conditional creates and consolidate them
*/
consolidateDuplicateConditionals(theEntries);
/*
* Loop through the request and process any entries of type
* PUT, POST or DELETE
*/
for (int i = 0; i < theEntries.size(); i++) {
if (i % 250 == 0) {
ourLog.debug("Processed {} non-GET entries out of {} in transaction", i, theEntries.size());
}
IBase nextReqEntry = theEntries.get(i);
IBaseResource res = myVersionAdapter.getResource(nextReqEntry);
IIdType nextResourceId = getNextResourceIdFromBaseResource(res, nextReqEntry, theAllIds);
String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry); String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry);
String resourceType = res != null ? myContext.getResourceType(res) : null; String resourceType = res != null ? myContext.getResourceType(res) : null;
Integer order = theOriginalRequestOrder.get(nextReqEntry); Integer order = theOriginalRequestOrder.get(nextReqEntry);
@ -904,7 +955,8 @@ public abstract class BaseTransactionProcessor {
} }
} }
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId,
outcome, nextRespEntry, resourceType, res, theRequest);
entriesToProcess.put(nextRespEntry, outcome.getId()); entriesToProcess.put(nextRespEntry, outcome.getId());
break; break;
} }
@ -971,52 +1023,24 @@ public abstract class BaseTransactionProcessor {
* was also deleted as a part of this transaction, which is why we check this now at the * was also deleted as a part of this transaction, which is why we check this now at the
* end. * end.
*/ */
for (Iterator<DeleteConflict> iter = deleteConflicts.iterator(); iter.hasNext(); ) { checkForDeleteConflicts(deleteConflicts, deletedResources, updatedResources);
DeleteConflict nextDeleteConflict = iter.next();
/* theIdToPersistedOutcome.entrySet().forEach(idAndOutcome -> {
* If we have a conflict, it means we can't delete Resource/A because theTransactionDetails.addResolvedResourceId(idAndOutcome.getKey(), idAndOutcome.getValue().getPersistentId());
* Resource/B has a reference to it. We'll ignore that conflict though });
* if it turns out we're also deleting Resource/B in this transaction.
*/
if (deletedResources.contains(nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue())) {
iter.remove();
continue;
}
/*
* And then, this is kind of a last ditch check. It's also ok to delete
* Resource/A if Resource/B isn't being deleted, but it is being UPDATED
* in this transaction, and the updated version of it has no references
* to Resource/A any more.
*/
String sourceId = nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue();
String targetId = nextDeleteConflict.getTargetId().toUnqualifiedVersionless().getValue();
Optional<IBaseResource> updatedSource = updatedResources
.stream()
.filter(t -> sourceId.equals(t.getIdElement().toUnqualifiedVersionless().getValue()))
.findFirst();
if (updatedSource.isPresent()) {
List<ResourceReferenceInfo> referencesInSource = myContext.newTerser().getAllResourceReferences(updatedSource.get());
boolean sourceStillReferencesTarget = referencesInSource
.stream()
.anyMatch(t -> targetId.equals(t.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue()));
if (!sourceStillReferencesTarget) {
iter.remove();
}
}
}
DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(myContext, deleteConflicts);
theIdToPersistedOutcome.entrySet().forEach(t -> theTransactionDetails.addResolvedResourceId(t.getKey(), t.getValue().getPersistentId()));
/* /*
* Perform ID substitutions and then index each resource we have saved * Perform ID substitutions and then index each resource we have saved
*/ */
resolveReferencesThenSaveAndIndexResources(theRequest, theTransactionDetails, theIdSubstitutions, theIdToPersistedOutcome, theTransactionStopWatch, entriesToProcess, nonUpdatedEntities, updatedEntities); resolveReferencesThenSaveAndIndexResources(theRequest, theTransactionDetails,
theIdSubstitutions, theIdToPersistedOutcome,
theTransactionStopWatch, entriesToProcess,
nonUpdatedEntities, updatedEntities);
theTransactionStopWatch.endCurrentTask(); theTransactionStopWatch.endCurrentTask();
// flush writes to db
theTransactionStopWatch.startTask("Flush writes to database"); theTransactionStopWatch.startTask("Flush writes to database");
flushSession(theIdToPersistedOutcome); flushSession(theIdToPersistedOutcome);
@ -1070,6 +1094,53 @@ public abstract class BaseTransactionProcessor {
} }
} }
/**
* Checks for any delete conflicts.
* @param theDeleteConflicts - set of delete conflicts
* @param theDeletedResources - set of deleted resources
* @param theUpdatedResources - list of updated resources
*/
private void checkForDeleteConflicts(DeleteConflictList theDeleteConflicts,
Set<String> theDeletedResources,
List<IBaseResource> theUpdatedResources) {
for (Iterator<DeleteConflict> iter = theDeleteConflicts.iterator(); iter.hasNext(); ) {
DeleteConflict nextDeleteConflict = iter.next();
/*
* If we have a conflict, it means we can't delete Resource/A because
* Resource/B has a reference to it. We'll ignore that conflict though
* if it turns out we're also deleting Resource/B in this transaction.
*/
if (theDeletedResources.contains(nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue())) {
iter.remove();
continue;
}
/*
* And then, this is kind of a last ditch check. It's also ok to delete
* Resource/A if Resource/B isn't being deleted, but it is being UPDATED
* in this transaction, and the updated version of it has no references
* to Resource/A any more.
*/
String sourceId = nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue();
String targetId = nextDeleteConflict.getTargetId().toUnqualifiedVersionless().getValue();
Optional<IBaseResource> updatedSource = theUpdatedResources
.stream()
.filter(t -> sourceId.equals(t.getIdElement().toUnqualifiedVersionless().getValue()))
.findFirst();
if (updatedSource.isPresent()) {
List<ResourceReferenceInfo> referencesInSource = myContext.newTerser().getAllResourceReferences(updatedSource.get());
boolean sourceStillReferencesTarget = referencesInSource
.stream()
.anyMatch(t -> targetId.equals(t.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue()));
if (!sourceStillReferencesTarget) {
iter.remove();
}
}
}
DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(myContext, theDeleteConflicts);
}
/** /**
* This method replaces any placeholder references in the * This method replaces any placeholder references in the
* source transaction Bundle with their actual targets, then stores the resource contents and indexes * source transaction Bundle with their actual targets, then stores the resource contents and indexes
@ -1092,7 +1163,10 @@ public abstract class BaseTransactionProcessor {
* pass because it's too complex to try and insert the auto-versioned references and still * pass because it's too complex to try and insert the auto-versioned references and still
* account for NOPs, so we block NOPs in that pass. * account for NOPs, so we block NOPs in that pass.
*/ */
private void resolveReferencesThenSaveAndIndexResources(RequestDetails theRequest, TransactionDetails theTransactionDetails, Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, StopWatch theTransactionStopWatch, Map<IBase, IIdType> entriesToProcess, Set<IIdType> nonUpdatedEntities, Set<IBasePersistedResource> updatedEntities) { private void resolveReferencesThenSaveAndIndexResources(RequestDetails theRequest, TransactionDetails theTransactionDetails,
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
StopWatch theTransactionStopWatch, Map<IBase, IIdType> entriesToProcess,
Set<IIdType> nonUpdatedEntities, Set<IBasePersistedResource> updatedEntities) {
FhirTerser terser = myContext.newTerser(); FhirTerser terser = myContext.newTerser();
theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources"); theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources");
IdentityHashMap<DaoMethodOutcome, Set<IBaseReference>> deferredIndexesForAutoVersioning = null; IdentityHashMap<DaoMethodOutcome, Set<IBaseReference>> deferredIndexesForAutoVersioning = null;
@ -1114,8 +1188,15 @@ public abstract class BaseTransactionProcessor {
Set<IBaseReference> referencesToAutoVersion = BaseStorageDao.extractReferencesToAutoVersion(myContext, myModelConfig, nextResource); Set<IBaseReference> referencesToAutoVersion = BaseStorageDao.extractReferencesToAutoVersion(myContext, myModelConfig, nextResource);
if (referencesToAutoVersion.isEmpty()) { if (referencesToAutoVersion.isEmpty()) {
resolveReferencesThenSaveAndIndexResource(theRequest, theTransactionDetails, theIdSubstitutions, theIdToPersistedOutcome, entriesToProcess, nonUpdatedEntities, updatedEntities, terser, nextOutcome, nextResource, referencesToAutoVersion); // no references to autoversion - we can do the resolve and save now
resolveReferencesThenSaveAndIndexResource(theRequest, theTransactionDetails,
theIdSubstitutions, theIdToPersistedOutcome,
entriesToProcess, nonUpdatedEntities,
updatedEntities, terser,
nextOutcome, nextResource,
referencesToAutoVersion); // this is empty
} else { } else {
// we have autoversioned things to defer until later
if (deferredIndexesForAutoVersioning == null) { if (deferredIndexesForAutoVersioning == null) {
deferredIndexesForAutoVersioning = new IdentityHashMap<>(); deferredIndexesForAutoVersioning = new IdentityHashMap<>();
} }
@ -1129,12 +1210,24 @@ public abstract class BaseTransactionProcessor {
DaoMethodOutcome nextOutcome = nextEntry.getKey(); DaoMethodOutcome nextOutcome = nextEntry.getKey();
Set<IBaseReference> referencesToAutoVersion = nextEntry.getValue(); Set<IBaseReference> referencesToAutoVersion = nextEntry.getValue();
IBaseResource nextResource = nextOutcome.getResource(); IBaseResource nextResource = nextOutcome.getResource();
resolveReferencesThenSaveAndIndexResource(theRequest, theTransactionDetails, theIdSubstitutions, theIdToPersistedOutcome, entriesToProcess, nonUpdatedEntities, updatedEntities, terser, nextOutcome, nextResource, referencesToAutoVersion);
resolveReferencesThenSaveAndIndexResource(theRequest, theTransactionDetails,
theIdSubstitutions, theIdToPersistedOutcome,
entriesToProcess, nonUpdatedEntities,
updatedEntities, terser,
nextOutcome, nextResource,
referencesToAutoVersion);
} }
} }
} }
private void resolveReferencesThenSaveAndIndexResource(RequestDetails theRequest, TransactionDetails theTransactionDetails, Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, Map<IBase, IIdType> entriesToProcess, Set<IIdType> nonUpdatedEntities, Set<IBasePersistedResource> updatedEntities, FhirTerser terser, DaoMethodOutcome nextOutcome, IBaseResource nextResource, Set<IBaseReference> theReferencesToAutoVersion) { private void resolveReferencesThenSaveAndIndexResource(RequestDetails theRequest, TransactionDetails theTransactionDetails,
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
Map<IBase, IIdType> entriesToProcess, Set<IIdType> nonUpdatedEntities,
Set<IBasePersistedResource> updatedEntities, FhirTerser terser,
DaoMethodOutcome nextOutcome, IBaseResource nextResource,
Set<IBaseReference> theReferencesToAutoVersion) {
// References // References
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource); List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource);
for (ResourceReferenceInfo nextRef : allRefs) { for (ResourceReferenceInfo nextRef : allRefs) {
@ -1175,9 +1268,34 @@ public abstract class BaseTransactionProcessor {
} else if (nextId.getValue().startsWith("urn:")) { } else if (nextId.getValue().startsWith("urn:")) {
throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType()); throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType());
} else { } else {
// get a map of
// existing ids -> PID (for resources that exist in the DB)
// should this be allPartitions?
ResourcePersistentIdMap resourceVersionMap = myResourceVersionSvc.getLatestVersionIdsForResourceIds(RequestPartitionId.allPartitions(),
theReferencesToAutoVersion.stream()
.map(IBaseReference::getReferenceElement).collect(Collectors.toList()));
for (IBaseReference baseRef : theReferencesToAutoVersion) {
IIdType id = baseRef.getReferenceElement();
if (!resourceVersionMap.containsKey(id)
&& myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
// not in the db, but autocreateplaceholders is true
// so the version we'll set is "1" (since it will be
// created later)
String newRef = id.withVersion("1").getValue();
id.setValue(newRef);
} else {
// we will add the looked up info to the transaction
// for later
theTransactionDetails.addResolvedResourceId(id,
resourceVersionMap.getResourcePersistentId(id));
}
}
if (theReferencesToAutoVersion.contains(resourceReference)) { if (theReferencesToAutoVersion.contains(resourceReference)) {
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(nextId); DaoMethodOutcome outcome = theIdToPersistedOutcome.get(nextId);
if (!outcome.isNop() && !Boolean.TRUE.equals(outcome.getCreated())) {
if (outcome != null && !outcome.isNop() && !Boolean.TRUE.equals(outcome.getCreated())) {
addRollbackReferenceRestore(theTransactionDetails, resourceReference); addRollbackReferenceRestore(theTransactionDetails, resourceReference);
resourceReference.setReference(nextId.getValue()); resourceReference.setReference(nextId.getValue());
resourceReference.setResource(null); resourceReference.setResource(null);
@ -1565,30 +1683,29 @@ public abstract class BaseTransactionProcessor {
return theStatusCode + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode)); return theStatusCode + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
} }
public class BundleTask implements Runnable { public class RetriableBundleTask implements Runnable {
private CountDownLatch myCompletedLatch; private final CountDownLatch myCompletedLatch;
private RequestDetails myRequestDetails; private final RequestDetails myRequestDetails;
private IBase myNextReqEntry; private final IBase myNextReqEntry;
private Map<Integer, Object> myResponseMap; private final Map<Integer, Object> myResponseMap;
private int myResponseOrder; private final int myResponseOrder;
private boolean myNestedMode; private final boolean myNestedMode;
private BaseServerResponseException myLastSeenException;
protected BundleTask(CountDownLatch theCompletedLatch, RequestDetails theRequestDetails, Map<Integer, Object> theResponseMap, int theResponseOrder, IBase theNextReqEntry, boolean theNestedMode) { protected RetriableBundleTask(CountDownLatch theCompletedLatch, RequestDetails theRequestDetails, Map<Integer, Object> theResponseMap, int theResponseOrder, IBase theNextReqEntry, boolean theNestedMode) {
this.myCompletedLatch = theCompletedLatch; this.myCompletedLatch = theCompletedLatch;
this.myRequestDetails = theRequestDetails; this.myRequestDetails = theRequestDetails;
this.myNextReqEntry = theNextReqEntry; this.myNextReqEntry = theNextReqEntry;
this.myResponseMap = theResponseMap; this.myResponseMap = theResponseMap;
this.myResponseOrder = theResponseOrder; this.myResponseOrder = theResponseOrder;
this.myNestedMode = theNestedMode; this.myNestedMode = theNestedMode;
this.myLastSeenException = null;
} }
@Override private void processBatchEntry() {
public void run() {
BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder();
try {
IBaseBundle subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode()); IBaseBundle subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode());
myVersionAdapter.addEntry(subRequestBundle, (IBase) myNextReqEntry); myVersionAdapter.addEntry(subRequestBundle, myNextReqEntry);
IBaseBundle nextResponseBundle = processTransactionAsSubRequest(myRequestDetails, subRequestBundle, "Batch sub-request", myNestedMode); IBaseBundle nextResponseBundle = processTransactionAsSubRequest(myRequestDetails, subRequestBundle, "Batch sub-request", myNestedMode);
@ -1602,22 +1719,45 @@ public abstract class BaseTransactionProcessor {
IBase nextResponseBundleFirstEntry = (IBase) myVersionAdapter.getEntries(nextResponseBundle).get(0); IBase nextResponseBundleFirstEntry = (IBase) myVersionAdapter.getEntries(nextResponseBundle).get(0);
myResponseMap.put(myResponseOrder, nextResponseBundleFirstEntry); myResponseMap.put(myResponseOrder, nextResponseBundleFirstEntry);
} }
}
private boolean processBatchEntryWithRetry() {
int maxAttempts =3;
for (int attempt = 1;; attempt++) {
try {
processBatchEntry();
return true;
} catch (BaseServerResponseException e) { } catch (BaseServerResponseException e) {
caughtEx.setException(e); //If we catch a known and structured exception from HAPI, just fail.
myLastSeenException = e;
return false;
} catch (Throwable t) { } catch (Throwable t) {
myLastSeenException = new InternalErrorException(t);
//If we have caught a non-tag-storage failure we are unfamiliar with, or we have exceeded max attempts, exit.
if (!DaoFailureUtil.isTagStorageFailure(t) || attempt >= maxAttempts) {
ourLog.error("Failure during BATCH sub transaction processing", t); ourLog.error("Failure during BATCH sub transaction processing", t);
caughtEx.setException(new InternalErrorException(t)); return false;
}
}
}
} }
if (caughtEx.getException() != null) { @Override
// add exception to the response map public void run() {
myResponseMap.put(myResponseOrder, caughtEx); boolean success = processBatchEntryWithRetry();
if (!success) {
populateResponseMapWithLastSeenException();
} }
// checking for the parallelism // checking for the parallelism
ourLog.debug("processing bacth for {} is completed", myVersionAdapter.getEntryRequestUrl((IBase)myNextReqEntry)); ourLog.debug("processing batch for {} is completed", myVersionAdapter.getEntryRequestUrl(myNextReqEntry));
myCompletedLatch.countDown(); myCompletedLatch.countDown();
} }
private void populateResponseMapWithLastSeenException() {
BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder();
caughtEx.setException(myLastSeenException);
myResponseMap.put(myResponseOrder, caughtEx);
}
} }
} }

View File

@ -0,0 +1,18 @@
package ca.uhn.fhir.jpa.dao;
import org.apache.commons.lang3.StringUtils;
/**
* Utility class to help identify classes of failure.
*/
public class DaoFailureUtil {
public static boolean isTagStorageFailure(Throwable t) {
if (StringUtils.isBlank(t.getMessage())) {
return false;
} else {
String msg = t.getMessage().toLowerCase();
return msg.contains("hfj_tag_def") || msg.contains("hfj_res_tag");
}
}
}

View File

@ -100,6 +100,16 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
@Query("SELECT t.myVersion FROM ResourceTable t WHERE t.myId = :pid") @Query("SELECT t.myVersion FROM ResourceTable t WHERE t.myId = :pid")
Long findCurrentVersionByPid(@Param("pid") Long thePid); Long findCurrentVersionByPid(@Param("pid") Long thePid);
/**
* This query will return rows with the following values:
* Id (resource pid - long), ResourceType (Patient, etc), version (long)
* Order matters!
* @param pid - list of pids to get versions for
* @return
*/
@Query("SELECT t.myId, t.myResourceType, t.myVersion FROM ResourceTable t WHERE t.myId IN ( :pid )")
Collection<Object[]> getResourceVersionsForPid(@Param("pid") List<Long> pid);
@Query("SELECT t FROM ResourceTable t LEFT JOIN FETCH t.myForcedId WHERE t.myPartitionId.myPartitionId IS NULL AND t.myId = :pid") @Query("SELECT t FROM ResourceTable t LEFT JOIN FETCH t.myForcedId WHERE t.myPartitionId.myPartitionId IS NULL AND t.myId = :pid")
Optional<ResourceTable> readByPartitionIdNull(@Param("pid") Long theResourceId); Optional<ResourceTable> readByPartitionIdNull(@Param("pid") Long theResourceId);

View File

@ -94,7 +94,6 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theSourcePath); throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theSourcePath);
} }
resolvedResource = createdTableOpt.get(); resolvedResource = createdTableOpt.get();
} }

View File

@ -309,7 +309,7 @@ public class IdHelperService {
if (forcedId.isPresent()) { if (forcedId.isPresent()) {
retVal.setValue(theResourceType + '/' + forcedId.get()); retVal.setValue(theResourceType + '/' + forcedId.get());
} else { } else {
retVal.setValue(theResourceType + '/' + theId.toString()); retVal.setValue(theResourceType + '/' + theId);
} }
return retVal; return retVal;

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy; import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoFailureUtil;
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;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -93,10 +94,9 @@ public class HapiTransactionService {
* known to the system already, they'll both try to create a row in HFJ_TAG_DEF, * known to the system already, they'll both try to create a row in HFJ_TAG_DEF,
* which is the tag definition table. In that case, a constraint error will be * which is the tag definition table. In that case, a constraint error will be
* thrown by one of the client threads, so we auto-retry in order to avoid * thrown by one of the client threads, so we auto-retry in order to avoid
* annopying spurious failures for the client. * annoying spurious failures for the client.
*/ */
if (e.getMessage().contains("HFJ_TAG_DEF") || e.getMessage().contains("hfj_tag_def") || if (DaoFailureUtil.isTagStorageFailure(e)) {
e.getMessage().contains("HFJ_RES_TAG") || e.getMessage().contains("hfj_res_tag")) {
maxRetries = 3; maxRetries = 3;
} }

View File

@ -1087,7 +1087,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
ourLog.trace("Performing count"); ourLog.trace("Performing count");
ISearchBuilder sb = newSearchBuilder(); ISearchBuilder sb = newSearchBuilder();
Iterator<Long> countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest, myRequestPartitionId); Iterator<Long> countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest, myRequestPartitionId);
Long count = countIterator.hasNext() ? countIterator.next() : 0; Long count = countIterator.hasNext() ? countIterator.next() : 0L;
ourLog.trace("Got count {}", count); ourLog.trace("Got count {}", count);
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);

View File

@ -729,7 +729,7 @@ public class QueryStack {
return predicateBuilder.createPredicate(theRequest, theResourceName, theParamName, theList, theOperation, theRequestPartitionId); return predicateBuilder.createPredicate(theRequest, theResourceName, theParamName, theList, theOperation, theRequestPartitionId);
} }
private Condition createPredicateReferenceForContainedResource(@Nullable DbColumn theSourceJoinColumn, public Condition createPredicateReferenceForContainedResource(@Nullable DbColumn theSourceJoinColumn,
String theResourceName, String theParamName, RuntimeSearchParam theSearchParam, String theResourceName, String theParamName, RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation, List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation,
RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
@ -794,31 +794,31 @@ public class QueryStack {
switch (targetParamDefinition.getParamType()) { switch (targetParamDefinition.getParamType()) {
case DATE: case DATE:
containedCondition = createPredicateDate(null, theResourceName, spnamePrefix, targetParamDefinition, containedCondition = createPredicateDate(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId); orValues, theOperation, theRequestPartitionId);
break; break;
case NUMBER: case NUMBER:
containedCondition = createPredicateNumber(null, theResourceName, spnamePrefix, targetParamDefinition, containedCondition = createPredicateNumber(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId); orValues, theOperation, theRequestPartitionId);
break; break;
case QUANTITY: case QUANTITY:
containedCondition = createPredicateQuantity(null, theResourceName, spnamePrefix, targetParamDefinition, containedCondition = createPredicateQuantity(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId); orValues, theOperation, theRequestPartitionId);
break; break;
case STRING: case STRING:
containedCondition = createPredicateString(null, theResourceName, spnamePrefix, targetParamDefinition, containedCondition = createPredicateString(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId); orValues, theOperation, theRequestPartitionId);
break; break;
case TOKEN: case TOKEN:
containedCondition = createPredicateToken(null, theResourceName, spnamePrefix, targetParamDefinition, containedCondition = createPredicateToken(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId); orValues, theOperation, theRequestPartitionId);
break; break;
case COMPOSITE: case COMPOSITE:
containedCondition = createPredicateComposite(null, theResourceName, spnamePrefix, targetParamDefinition, containedCondition = createPredicateComposite(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theRequestPartitionId); orValues, theRequestPartitionId);
break; break;
case URI: case URI:
containedCondition = createPredicateUri(null, theResourceName, spnamePrefix, targetParamDefinition, containedCondition = createPredicateUri(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequest, theRequestPartitionId); orValues, theOperation, theRequest, theRequestPartitionId);
break; break;
case HAS: case HAS:
@ -1162,11 +1162,25 @@ public class QueryStack {
break; break;
case REFERENCE: case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
if (theSearchContainedMode.equals(SearchContainedModeEnum.TRUE)) if (theSearchContainedMode.equals(SearchContainedModeEnum.TRUE)) {
andPredicates.add(createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, theParamName, nextParamDef, nextAnd, null, theRequest, theRequestPartitionId)); // TODO: The _contained parameter is not intended to control search chain interpretation like this.
else // See SMILE-2898 for details.
// For now, leave the incorrect implementation alone, just in case someone is relying on it,
// until the complete fix is available.
andPredicates.add(createPredicateReferenceForContainedResource(null, theResourceName, theParamName, nextParamDef, nextAnd, null, theRequest, theRequestPartitionId));
} else if (isEligibleForContainedResourceSearch(nextAnd)) {
// TODO for now, restrict contained reference traversal to the last reference in the chain
// We don't seem to be indexing the outbound references of a contained resource, so we can't
// include them in search chains.
// It would be nice to eventually relax this constraint, but no client seems to be asking for it.
andPredicates.add(toOrPredicate(
createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId),
createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, theParamName, nextParamDef, nextAnd, null, theRequest, theRequestPartitionId)
));
} else {
andPredicates.add(createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId)); andPredicates.add(createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId));
} }
}
break; break;
case STRING: case STRING:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
@ -1243,6 +1257,14 @@ public class QueryStack {
return toAndPredicate(andPredicates); return toAndPredicate(andPredicates);
} }
private boolean isEligibleForContainedResourceSearch(List<? extends IQueryParameterType> nextAnd) {
return myModelConfig.isIndexOnContainedResources() &&
nextAnd.stream()
.filter(t -> t instanceof ReferenceParam)
.map(t -> (ReferenceParam) t)
.noneMatch(t -> t.getChain().contains("."));
}
public void addPredicateCompositeUnique(String theIndexString, RequestPartitionId theRequestPartitionId) { public void addPredicateCompositeUnique(String theIndexString, RequestPartitionId theRequestPartitionId) {
ComboUniqueSearchParameterPredicateBuilder predicateBuilder = mySqlBuilder.addComboUniquePredicateBuilder(); ComboUniqueSearchParameterPredicateBuilder predicateBuilder = mySqlBuilder.addComboUniquePredicateBuilder();
Condition predicate = predicateBuilder.createPredicateIndexString(theRequestPartitionId, theIndexString); Condition predicate = predicateBuilder.createPredicateIndexString(theRequestPartitionId, theIndexString);

View File

@ -377,7 +377,7 @@ public class SearchBuilder implements ISearchBuilder {
SearchQueryBuilder sqlBuilder = new SearchQueryBuilder(myContext, myDaoConfig.getModelConfig(), myPartitionSettings, myRequestPartitionId, sqlBuilderResourceName, mySqlBuilderFactory, myDialectProvider, theCount); SearchQueryBuilder sqlBuilder = new SearchQueryBuilder(myContext, myDaoConfig.getModelConfig(), myPartitionSettings, myRequestPartitionId, sqlBuilderResourceName, mySqlBuilderFactory, myDialectProvider, theCount);
QueryStack queryStack3 = new QueryStack(theParams, myDaoConfig, myDaoConfig.getModelConfig(), myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings); QueryStack queryStack3 = new QueryStack(theParams, myDaoConfig, myDaoConfig.getModelConfig(), myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings);
if (theParams.keySet().size() > 1 || theParams.getSort() != null || theParams.keySet().contains(Constants.PARAM_HAS)) { if (theParams.keySet().size() > 1 || theParams.getSort() != null || theParams.keySet().contains(Constants.PARAM_HAS) || isPotentiallyContainedReferenceParameterExistsAtRoot(theParams)) {
List<RuntimeSearchParam> activeComboParams = mySearchParamRegistry.getActiveComboSearchParams(myResourceName, theParams.keySet()); List<RuntimeSearchParam> activeComboParams = mySearchParamRegistry.getActiveComboSearchParams(myResourceName, theParams.keySet());
if (activeComboParams.isEmpty()) { if (activeComboParams.isEmpty()) {
sqlBuilder.setNeedResourceTableRoot(true); sqlBuilder.setNeedResourceTableRoot(true);
@ -487,6 +487,13 @@ public class SearchBuilder implements ISearchBuilder {
return Optional.of(executor); return Optional.of(executor);
} }
private boolean isPotentiallyContainedReferenceParameterExistsAtRoot(SearchParameterMap theParams) {
return myModelConfig.isIndexOnContainedResources() && theParams.values().stream()
.flatMap(Collection::stream)
.flatMap(Collection::stream)
.anyMatch(t -> t instanceof ReferenceParam);
}
private List<Long> normalizeIdListForLastNInClause(List<Long> lastnResourceIds) { private List<Long> normalizeIdListForLastNInClause(List<Long> lastnResourceIds) {
/* /*
The following is a workaround to a known issue involving Hibernate. If queries are used with "in" clauses with large and varying The following is a workaround to a known issue involving Hibernate. If queries are used with "in" clauses with large and varying

View File

@ -38,20 +38,18 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference; import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser; import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder; import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.CompositeParam;
@ -66,6 +64,8 @@ import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
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.ISearchParamRegistry;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.healthmarketscience.sqlbuilder.BinaryCondition; import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition; import com.healthmarketscience.sqlbuilder.ComboCondition;
@ -341,7 +341,7 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
List<Condition> orPredicates = new ArrayList<>(); List<Condition> orPredicates = new ArrayList<>();
boolean paramInverted = false; boolean paramInverted = false;
QueryStack childQueryFactory = myQueryStack.newChildQueryFactoryWithFullBuilderReuse(); QueryStack childQueryFactory = myQueryStack.newChildQueryFactoryWithFullBuilderReuse();
for (String nextType : resourceTypes) {
String chain = theReferenceParam.getChain(); String chain = theReferenceParam.getChain();
String remainingChain = null; String remainingChain = null;
@ -351,15 +351,6 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
chain = chain.substring(0, chainDotIndex); chain = chain.substring(0, chainDotIndex);
} }
RuntimeResourceDefinition typeDef = getFhirContext().getResourceDefinition(nextType);
String subResourceName = typeDef.getName();
IDao dao = myDaoRegistry.getResourceDao(nextType);
if (dao == null) {
ourLog.debug("Don't have a DAO for type {}", nextType);
continue;
}
int qualifierIndex = chain.indexOf(':'); int qualifierIndex = chain.indexOf(':');
String qualifier = null; String qualifier = null;
if (qualifierIndex != -1) { if (qualifierIndex != -1) {
@ -368,6 +359,18 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
} }
boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain); boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain);
for (String nextType : resourceTypes) {
RuntimeResourceDefinition typeDef = getFhirContext().getResourceDefinition(nextType);
String subResourceName = typeDef.getName();
IDao dao = myDaoRegistry.getResourceDao(nextType);
if (dao == null) {
ourLog.debug("Don't have a DAO for type {}", nextType);
continue;
}
RuntimeSearchParam param = null; RuntimeSearchParam param = null;
if (!isMeta) { if (!isMeta) {
param = mySearchParamRegistry.getActiveSearchParam(nextType, chain); param = mySearchParamRegistry.getActiveSearchParam(nextType, chain);
@ -408,7 +411,6 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
andPredicates.add(childQueryFactory.searchForIdsWithAndOr(myColumnTargetResourceId, subResourceName, chain, chainParamValues, theRequest, theRequestPartitionId, SearchContainedModeEnum.FALSE)); andPredicates.add(childQueryFactory.searchForIdsWithAndOr(myColumnTargetResourceId, subResourceName, chain, chainParamValues, theRequest, theRequestPartitionId, SearchContainedModeEnum.FALSE));
orPredicates.add(toAndPredicate(andPredicates)); orPredicates.add(toAndPredicate(andPredicates));
} }
if (candidateTargetTypes.isEmpty()) { if (candidateTargetTypes.isEmpty()) {

View File

@ -37,6 +37,7 @@ import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexSettings
import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName; import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName;
import org.slf4j.Logger; import org.slf4j.Logger;
import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Properties; import java.util.Properties;
@ -52,10 +53,10 @@ public class ElasticsearchHibernatePropertiesBuilder {
private static final Logger ourLog = getLogger(ElasticsearchHibernatePropertiesBuilder.class); private static final Logger ourLog = getLogger(ElasticsearchHibernatePropertiesBuilder.class);
private IndexStatus myRequiredIndexStatus = IndexStatus.YELLOW.YELLOW; private IndexStatus myRequiredIndexStatus = IndexStatus.YELLOW;
private SchemaManagementStrategyName myIndexSchemaManagementStrategy = SchemaManagementStrategyName.CREATE; private SchemaManagementStrategyName myIndexSchemaManagementStrategy = SchemaManagementStrategyName.CREATE;
private String myRestUrl; private String myHosts;
private String myUsername; private String myUsername;
private String myPassword; private String myPassword;
private long myIndexManagementWaitTimeoutMillis = 10000L; private long myIndexManagementWaitTimeoutMillis = 10000L;
@ -77,11 +78,8 @@ public class ElasticsearchHibernatePropertiesBuilder {
// the below properties are used for ElasticSearch integration // the below properties are used for ElasticSearch integration
theProperties.put(BackendSettings.backendKey(BackendSettings.TYPE), "elasticsearch"); theProperties.put(BackendSettings.backendKey(BackendSettings.TYPE), "elasticsearch");
theProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.ANALYSIS_CONFIGURER), HapiElasticsearchAnalysisConfigurer.class.getName()); theProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.ANALYSIS_CONFIGURER), HapiElasticsearchAnalysisConfigurer.class.getName());
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.HOSTS), myHosts);
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.HOSTS), myRestUrl);
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.PROTOCOL), myProtocol); theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.PROTOCOL), myProtocol);
if (StringUtils.isNotBlank(myUsername)) { if (StringUtils.isNotBlank(myUsername)) {
@ -99,8 +97,10 @@ public class ElasticsearchHibernatePropertiesBuilder {
theProperties.put(HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, myDebugSyncStrategy); theProperties.put(HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, myDebugSyncStrategy);
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.LOG_JSON_PRETTY_PRINTING), Boolean.toString(myDebugPrettyPrintJsonLog)); theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.LOG_JSON_PRETTY_PRINTING), Boolean.toString(myDebugPrettyPrintJsonLog));
injectStartupTemplate(myProtocol, myRestUrl, myUsername, myPassword); //This tells elasticsearch to use our custom index naming strategy.
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.LAYOUT_STRATEGY), IndexNamePrefixLayoutStrategy.class.getName());
injectStartupTemplate(myProtocol, myHosts, myUsername, myPassword);
} }
public ElasticsearchHibernatePropertiesBuilder setRequiredIndexStatus(IndexStatus theRequiredIndexStatus) { public ElasticsearchHibernatePropertiesBuilder setRequiredIndexStatus(IndexStatus theRequiredIndexStatus) {
@ -108,11 +108,8 @@ public class ElasticsearchHibernatePropertiesBuilder {
return this; return this;
} }
public ElasticsearchHibernatePropertiesBuilder setRestUrl(String theRestUrl) { public ElasticsearchHibernatePropertiesBuilder setHosts(String hosts) {
if (theRestUrl.contains("://")) { myHosts = hosts;
throw new ConfigurationException("Elasticsearch URL cannot include a protocol, that is a separate property. Remove http:// or https:// from this URL.");
}
myRestUrl = theRestUrl;
return this; return this;
} }
@ -147,18 +144,13 @@ public class ElasticsearchHibernatePropertiesBuilder {
* TODO GGG HS: In HS6.1, we should have a native way of performing index settings manipulation at bootstrap time, so this should * TODO GGG HS: In HS6.1, we should have a native way of performing index settings manipulation at bootstrap time, so this should
* eventually be removed in favour of whatever solution they come up with. * eventually be removed in favour of whatever solution they come up with.
*/ */
void injectStartupTemplate(String theProtocol, String theHostAndPort, String theUsername, String thePassword) { void injectStartupTemplate(String theProtocol, String theHosts, @Nullable String theUsername, @Nullable String thePassword) {
PutIndexTemplateRequest ngramTemplate = new PutIndexTemplateRequest("ngram-template") PutIndexTemplateRequest ngramTemplate = new PutIndexTemplateRequest("ngram-template")
.patterns(Arrays.asList("resourcetable-*", "termconcept-*")) .patterns(Arrays.asList("*resourcetable-*", "*termconcept-*"))
.settings(Settings.builder().put("index.max_ngram_diff", 50)); .settings(Settings.builder().put("index.max_ngram_diff", 50));
int colonIndex = theHostAndPort.indexOf(":");
String host = theHostAndPort.substring(0, colonIndex);
Integer port = Integer.valueOf(theHostAndPort.substring(colonIndex + 1));
String qualifiedHost = theProtocol + "://" + host;
try { try {
RestHighLevelClient elasticsearchHighLevelRestClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(qualifiedHost, port, theUsername, thePassword); RestHighLevelClient elasticsearchHighLevelRestClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(theProtocol, theHosts, theUsername, thePassword);
ourLog.info("Adding starter template for large ngram diffs"); ourLog.info("Adding starter template for large ngram diffs");
AcknowledgedResponse acknowledgedResponse = elasticsearchHighLevelRestClient.indices().putTemplate(ngramTemplate, RequestOptions.DEFAULT); AcknowledgedResponse acknowledgedResponse = elasticsearchHighLevelRestClient.indices().putTemplate(ngramTemplate, RequestOptions.DEFAULT);
assert acknowledgedResponse.isAcknowledged(); assert acknowledgedResponse.isAcknowledged();

View File

@ -0,0 +1,99 @@
package ca.uhn.fhir.jpa.search.elastic;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 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.context.ConfigurationException;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy;
import org.hibernate.search.backend.elasticsearch.logging.impl.Log;
import org.hibernate.search.util.common.logging.impl.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.lang.invoke.MethodHandles;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class instructs hibernate search on how to create index names for indexed entities.
* In our case, we use this class to add an optional prefix to all indices which are created, which can be controlled via
* {@link DaoConfig#setElasticSearchIndexPrefix(String)}.
*/
@Service
public class IndexNamePrefixLayoutStrategy implements IndexLayoutStrategy {
@Autowired
private DaoConfig myDaoConfig;
static final Log log = LoggerFactory.make(Log.class, MethodHandles.lookup());
public static final String NAME = "prefix";
public static final Pattern UNIQUE_KEY_EXTRACTION_PATTERN = Pattern.compile("(.*)-\\d{6}");
public String createInitialElasticsearchIndexName(String hibernateSearchIndexName) {
return addPrefixIfNecessary(hibernateSearchIndexName + "-000001");
}
public String createWriteAlias(String hibernateSearchIndexName) {
return addPrefixIfNecessary(hibernateSearchIndexName +"-write");
}
public String createReadAlias(String hibernateSearchIndexName) {
return addPrefixIfNecessary(hibernateSearchIndexName + "-read");
}
private String addPrefixIfNecessary(String theCandidateName) {
validateDaoConfigIsPresent();
if (!StringUtils.isBlank(myDaoConfig.getElasticSearchIndexPrefix())) {
return myDaoConfig.getElasticSearchIndexPrefix() + "-" + theCandidateName;
} else {
return theCandidateName;
}
}
public String extractUniqueKeyFromHibernateSearchIndexName(String hibernateSearchIndexName) {
return hibernateSearchIndexName;
}
public String extractUniqueKeyFromElasticsearchIndexName(String elasticsearchIndexName) {
Matcher matcher = UNIQUE_KEY_EXTRACTION_PATTERN.matcher(elasticsearchIndexName);
if (!matcher.matches()) {
throw log.invalidIndexPrimaryName(elasticsearchIndexName, UNIQUE_KEY_EXTRACTION_PATTERN);
} else {
String candidateUniqueKey= matcher.group(1);
return removePrefixIfNecessary(candidateUniqueKey);
}
}
private String removePrefixIfNecessary(String theCandidateUniqueKey) {
validateDaoConfigIsPresent();
if (!StringUtils.isBlank(myDaoConfig.getElasticSearchIndexPrefix())) {
return theCandidateUniqueKey.replace(myDaoConfig.getElasticSearchIndexPrefix() + "-", "");
} else {
return theCandidateUniqueKey;
}
}
private void validateDaoConfigIsPresent() {
if (myDaoConfig == null) {
throw new ConfigurationException("While attempting to boot HAPI FHIR, the Hibernate Search bootstrapper failed to find the DaoConfig. This probably means Hibernate Search has been recently upgraded, or somebody modified HapiFhirLocalContainerEntityManagerFactoryBean.");
}
}
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.search.lastn;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.ConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope; import org.apache.http.auth.AuthScope;
@ -27,41 +29,47 @@ import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider; import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeader;
import org.elasticsearch.client.Node;
import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.RestHighLevelClient;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ElasticsearchRestClientFactory { public class ElasticsearchRestClientFactory {
private static String determineScheme(String theHostname) { static public RestHighLevelClient createElasticsearchHighLevelRestClient(
int schemeIdx = theHostname.indexOf("://"); String protocol, String hosts, @Nullable String theUsername, @Nullable String thePassword) {
if (schemeIdx > 0) {
return theHostname.substring(0, schemeIdx); if (hosts.contains("://")) {
} else { throw new ConfigurationException("Elasticsearch URLs cannot include a protocol, that is a separate property. Remove http:// or https:// from this URL.");
return "http";
} }
String[] hostArray = hosts.split(",");
List<Node> clientNodes = Arrays.stream(hostArray)
.map(String::trim)
.filter(s -> s.contains(":"))
.map(h -> {
int colonIndex = h.indexOf(":");
String host = h.substring(0, colonIndex);
int port = Integer.parseInt(h.substring(colonIndex + 1));
return new Node(new HttpHost(host, port, protocol));
})
.collect(Collectors.toList());
if (hostArray.length != clientNodes.size()) {
throw new ConfigurationException("Elasticsearch URLs have to contain ':' as a host:port separator. Example: localhost:9200,localhost:9201,localhost:9202");
} }
private static String stripHostOfScheme(String theHostname) { RestClientBuilder clientBuilder = RestClient.builder(clientNodes.toArray(new Node[0]));
int schemeIdx = theHostname.indexOf("://"); if (StringUtils.isNotBlank(theUsername) && StringUtils.isNotBlank(thePassword)) {
if (schemeIdx > 0) { final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
return theHostname.substring(schemeIdx + 3); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(theUsername, thePassword));
} else { clientBuilder.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
return theHostname;
}
}
static public RestHighLevelClient createElasticsearchHighLevelRestClient(String theHostname, int thePort, String theUsername, String thePassword) {
final CredentialsProvider credentialsProvider =
new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(theUsername, thePassword));
RestClientBuilder clientBuilder = RestClient.builder(
new HttpHost(stripHostOfScheme(theHostname), thePort, determineScheme(theHostname)))
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider)); .setDefaultCredentialsProvider(credentialsProvider));
}
Header[] defaultHeaders = new Header[]{new BasicHeader("Content-Type", "application/json")}; Header[] defaultHeaders = new Header[]{new BasicHeader("Content-Type", "application/json")};
clientBuilder.setDefaultHeaders(defaultHeaders); clientBuilder.setDefaultHeaders(defaultHeaders);

View File

@ -68,11 +68,11 @@ import org.elasticsearch.search.aggregations.bucket.terms.ParsedTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedTopHits; import org.elasticsearch.search.aggregations.metrics.ParsedTopHits;
import org.elasticsearch.search.aggregations.support.ValueType;
import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -125,13 +125,13 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
private PartitionSettings myPartitionSettings; private PartitionSettings myPartitionSettings;
//This constructor used to inject a dummy partitionsettings in test. //This constructor used to inject a dummy partitionsettings in test.
public ElasticsearchSvcImpl(PartitionSettings thePartitionSetings, String theHostname, int thePort, String theUsername, String thePassword) { public ElasticsearchSvcImpl(PartitionSettings thePartitionSetings, String theHostname, @Nullable String theUsername, @Nullable String thePassword) {
this(theHostname, thePort, theUsername, thePassword); this(theHostname, theUsername, thePassword);
this.myPartitionSettings = thePartitionSetings; this.myPartitionSettings = thePartitionSetings;
} }
public ElasticsearchSvcImpl(String theHostname, int thePort, String theUsername, String thePassword) { public ElasticsearchSvcImpl(String theHostname, @Nullable String theUsername, @Nullable String thePassword) {
myRestHighLevelClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(theHostname, thePort, theUsername, thePassword); myRestHighLevelClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient("http", theHostname, theUsername, thePassword);
try { try {
createObservationIndexIfMissing(); createObservationIndexIfMissing();

View File

@ -126,5 +126,8 @@ public class SqlQuery {
} }
@Override
public String toString() {
return getSql(true, true);
}
} }

View File

@ -21,12 +21,12 @@ public class ResourceVersionCacheSvcTest extends BaseJpaR4Test {
IIdType patientId = myPatientDao.create(patient).getId(); IIdType patientId = myPatientDao.create(patient).getId();
ResourceVersionMap versionMap = myResourceVersionCacheSvc.getVersionMap("Patient", SearchParameterMap.newSynchronous()); ResourceVersionMap versionMap = myResourceVersionCacheSvc.getVersionMap("Patient", SearchParameterMap.newSynchronous());
assertEquals(1, versionMap.size()); assertEquals(1, versionMap.size());
assertEquals("1", versionMap.getVersion(patientId)); assertEquals(1L, versionMap.getVersion(patientId));
patient.setGender(Enumerations.AdministrativeGender.MALE); patient.setGender(Enumerations.AdministrativeGender.MALE);
myPatientDao.update(patient); myPatientDao.update(patient);
versionMap = myResourceVersionCacheSvc.getVersionMap("Patient", SearchParameterMap.newSynchronous()); versionMap = myResourceVersionCacheSvc.getVersionMap("Patient", SearchParameterMap.newSynchronous());
assertEquals(1, versionMap.size()); assertEquals(1, versionMap.size());
assertEquals("2", versionMap.getVersion(patientId)); assertEquals(2L, versionMap.getVersion(patientId));
} }
} }

View File

@ -0,0 +1,147 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.BlockLargeNumbersOfParamsListener;
import ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect;
import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
import ca.uhn.fhir.jpa.dao.r4.ElasticsearchPrefixTest;
import ca.uhn.fhir.jpa.search.elastic.HapiElasticsearchAnalysisConfigurer;
import ca.uhn.fhir.jpa.search.elastic.IndexNamePrefixLayoutStrategy;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchRestClientFactory;
import ca.uhn.fhir.jpa.search.lastn.config.TestElasticsearchContainerHelper;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.common.settings.Settings;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchBackendSettings;
import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexSettings;
import org.hibernate.search.backend.elasticsearch.index.IndexStatus;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
/**
* The only reason this is its own class is so that we can set a dao config setting before the whole test framework comes online.
* We need to do this as it is during bean creation that HS bootstrapping occurs.
*/
@Configuration
public class ElasticsearchWithPrefixConfig {
@Bean
public DaoConfig daoConfig() {
DaoConfig daoConfig = new DaoConfig();
daoConfig.setElasticSearchIndexPrefix(ElasticsearchPrefixTest.ELASTIC_PREFIX);
return daoConfig;
}
@Bean
public IndexNamePrefixLayoutStrategy indexNamePrefixLayoutStrategy() {
return new IndexNamePrefixLayoutStrategy();
}
@Bean
public FhirContext fhirContext() {
return FhirContext.forR4();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = new HapiFhirLocalContainerEntityManagerFactoryBean(theConfigurableListableBeanFactory);
retVal.setJpaDialect(new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer()));
retVal.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
retVal.setPersistenceUnitName("PU_HapiFhirJpaR4");
retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties());
return retVal;
}
@Bean
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource();
retVal.setDriver(new org.h2.Driver());
retVal.setUrl("jdbc:h2:mem:testdb_r4");
retVal.setMaxWaitMillis(30000);
retVal.setUsername("");
retVal.setPassword("");
retVal.setMaxTotal(5);
SLF4JLogLevel level = SLF4JLogLevel.INFO;
DataSource dataSource = ProxyDataSourceBuilder
.create(retVal)
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS, level)
.beforeQuery(new BlockLargeNumbersOfParamsListener())
.afterQuery(new CurrentThreadCaptureQueriesListener())
.build();
return dataSource;
}
@Bean
public Properties jpaProperties() {
Properties extraProperties = new Properties();
extraProperties.put("hibernate.format_sql", "false");
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", H2Dialect.class.getName());
//Override default lucene settings
// Force elasticsearch to start first
int httpPort = elasticContainer().getMappedPort(9200);//9200 is the HTTP port
String host = elasticContainer().getHost();
// the below properties are used for ElasticSearch integration
extraProperties.put(BackendSettings.backendKey(BackendSettings.TYPE), "elasticsearch");
extraProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.ANALYSIS_CONFIGURER), HapiElasticsearchAnalysisConfigurer.class.getName());
extraProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.HOSTS), host + ":" + httpPort);
extraProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.PROTOCOL), "http");
extraProperties.put(HibernateOrmMapperSettings.SCHEMA_MANAGEMENT_STRATEGY, SchemaManagementStrategyName.CREATE.externalRepresentation());
extraProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS_WAIT_TIMEOUT), Long.toString(10000));
extraProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS), IndexStatus.YELLOW.externalRepresentation());
// Need the mapping to be dynamic because of terminology indexes.
extraProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.DYNAMIC_MAPPING), "true");
// Only for unit tests
extraProperties.put(HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, "read-sync");
extraProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.LOG_JSON_PRETTY_PRINTING), Boolean.toString(true));
//This tells elasticsearch to use our custom index naming strategy.
extraProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.LAYOUT_STRATEGY), IndexNamePrefixLayoutStrategy.class.getName());
PutIndexTemplateRequest ngramTemplate = new PutIndexTemplateRequest("ngram-template")
.patterns(Arrays.asList("*resourcetable-*", "*termconcept-*"))
.settings(Settings.builder().put("index.max_ngram_diff", 50));
try {
RestHighLevelClient elasticsearchHighLevelRestClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient("http", host + ":" + httpPort, "", "");
AcknowledgedResponse acknowledgedResponse = elasticsearchHighLevelRestClient.indices().putTemplate(ngramTemplate, RequestOptions.DEFAULT);
assert acknowledgedResponse.isAcknowledged();
} catch (IOException theE) {
theE.printStackTrace();
throw new ConfigurationException("Couldn't connect to the elasticsearch server to create necessary templates. Ensure the Elasticsearch user has permissions to create templates.");
}
return extraProperties;
}
@Bean
public ElasticsearchContainer elasticContainer() {
ElasticsearchContainer embeddedElasticSearch = TestElasticsearchContainerHelper.getEmbeddedElasticSearch();
embeddedElasticSearch.start();
return embeddedElasticSearch;
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -134,8 +135,8 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu2"); retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu2");
retVal.setDataSource(dataSource()); retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties()); retVal.setJpaProperties(jpaProperties());

View File

@ -15,6 +15,7 @@ import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings; import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings; import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -138,8 +139,8 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu3"); retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu3");
retVal.setDataSource(dataSource()); retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties()); retVal.setJpaProperties(jpaProperties());

View File

@ -3,12 +3,14 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.elastic.IndexNamePrefixLayoutStrategy;
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber;
import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig;
import ca.uhn.fhir.test.utilities.BatchJobHelper; import ca.uhn.fhir.test.utilities.BatchJobHelper;
import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy;
import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.batch.BatchJobsConfig; import ca.uhn.fhir.jpa.batch.BatchJobsConfig;
import ca.uhn.fhir.jpa.batch.api.IBatchJobSubmitter; import ca.uhn.fhir.jpa.batch.api.IBatchJobSubmitter;
import ca.uhn.fhir.jpa.batch.svc.BatchJobSubmitterImpl; import ca.uhn.fhir.jpa.batch.svc.BatchJobSubmitterImpl;
@ -14,6 +15,8 @@ import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.H2Dialect;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -138,10 +141,15 @@ public class TestR4Config extends BaseJavaConfigR4 {
return new SingleQueryCountHolder(); return new SingleQueryCountHolder();
} }
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = new HapiFhirLocalContainerEntityManagerFactoryBean(theConfigurableListableBeanFactory);
configureEntityManagerFactory(retVal, fhirContext());
retVal.setJpaDialect(new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer()));
retVal.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
retVal.setPersistenceUnitName("PU_HapiFhirJpaR4"); retVal.setPersistenceUnitName("PU_HapiFhirJpaR4");
retVal.setDataSource(dataSource()); retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties()); retVal.setJpaProperties(jpaProperties());

View File

@ -1,13 +1,19 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder; import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
import ca.uhn.fhir.jpa.search.elastic.IndexNamePrefixLayoutStrategy;
import ca.uhn.fhir.jpa.search.lastn.config.TestElasticsearchContainerHelper; import ca.uhn.fhir.jpa.search.lastn.config.TestElasticsearchContainerHelper;
import org.h2.index.Index;
import org.hibernate.search.backend.elasticsearch.index.IndexStatus; import org.hibernate.search.backend.elasticsearch.index.IndexStatus;
import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy;
import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName; import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName;
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.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.testcontainers.elasticsearch.ElasticsearchContainer; import org.testcontainers.elasticsearch.ElasticsearchContainer;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -35,7 +41,7 @@ public class TestR4ConfigWithElasticSearch extends TestR4Config {
.setIndexSchemaManagementStrategy(SchemaManagementStrategyName.CREATE) .setIndexSchemaManagementStrategy(SchemaManagementStrategyName.CREATE)
.setIndexManagementWaitTimeoutMillis(10000) .setIndexManagementWaitTimeoutMillis(10000)
.setRequiredIndexStatus(IndexStatus.YELLOW) .setRequiredIndexStatus(IndexStatus.YELLOW)
.setRestUrl(host+ ":" + httpPort) .setHosts(host + ":" + httpPort)
.setProtocol("http") .setProtocol("http")
.setUsername("") .setUsername("")
.setPassword("") .setPassword("")

View File

@ -21,7 +21,7 @@ public class TestR4ConfigWithElasticsearchClient extends TestR4ConfigWithElastic
public ElasticsearchSvcImpl myElasticsearchSvc() { public ElasticsearchSvcImpl myElasticsearchSvc() {
int elasticsearchPort = elasticContainer().getMappedPort(9200); int elasticsearchPort = elasticContainer().getMappedPort(9200);
String host = elasticContainer().getHost(); String host = elasticContainer().getHost();
return new ElasticsearchSvcImpl(host, elasticsearchPort, "", ""); return new ElasticsearchSvcImpl(host + ":" + elasticsearchPort, null, null);
} }
@PreDestroy @PreDestroy

View File

@ -3,9 +3,11 @@ package ca.uhn.fhir.jpa.config;
import java.util.Properties; import java.util.Properties;
import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.H2Dialect;
import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy;
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings; import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.engine.cfg.BackendSettings; import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@ -27,8 +29,8 @@ public class TestR4WithLuceneDisabledConfig extends TestR4Config {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setDataSource(dataSource()); retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties()); retVal.setJpaProperties(jpaProperties());
return retVal; return retVal;

View File

@ -16,6 +16,7 @@ import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings; import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -138,8 +139,8 @@ public class TestR5Config extends BaseJavaConfigR5 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("PU_HapiFhirJpaR5"); retVal.setPersistenceUnitName("PU_HapiFhirJpaR5");
retVal.setDataSource(dataSource()); retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties()); retVal.setJpaProperties(jpaProperties());

View File

@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
class BaseHapiFhirResourceDaoTest { class BaseHapiFhirResourceDaoTest {
TestResourceDao mySvc = new TestResourceDao(); TestResourceDao mySvc = new TestResourceDao();
@Test @Test

View File

@ -721,6 +721,4 @@ public abstract class BaseJpaTest extends BaseTest {
} }
Thread.sleep(500); Thread.sleep(500);
} }
} }

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.executor.InterceptorService; import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
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.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4; import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
@ -70,6 +71,8 @@ public class TransactionProcessorTest {
private MatchUrlService myMatchUrlService; private MatchUrlService myMatchUrlService;
@MockBean @MockBean
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc; private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
@MockBean
private IResourceVersionSvc myResourceVersionSvc;
@MockBean(answer = Answers.RETURNS_DEEP_STUBS) @MockBean(answer = Answers.RETURNS_DEEP_STUBS)
private SessionImpl mySession; private SessionImpl mySession;

View File

@ -1,35 +0,0 @@
package ca.uhn.fhir.jpa.dao.index;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class IdHelperServiceTest {
@Test
public void testReplaceDefault_AllPartitions() {
IdHelperService svc = new IdHelperService();
PartitionSettings partitionSettings = new PartitionSettings();
partitionSettings.setDefaultPartitionId(1);
svc.setPartitionSettingsForUnitTest(partitionSettings);
RequestPartitionId outcome = svc.replaceDefault(RequestPartitionId.allPartitions());
assertSame(RequestPartitionId.allPartitions(), outcome);
}
@Test
public void testReplaceDefault_DefaultPartition() {
IdHelperService svc = new IdHelperService();
PartitionSettings partitionSettings = new PartitionSettings();
partitionSettings.setDefaultPartitionId(1);
svc.setPartitionSettingsForUnitTest(partitionSettings);
RequestPartitionId outcome = svc.replaceDefault(RequestPartitionId.defaultPartition());
assertEquals(1, outcome.getPartitionIds().get(0));
}
}

View File

@ -0,0 +1,209 @@
package ca.uhn.fhir.jpa.dao.index;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.cache.ResourcePersistentIdMap;
import ca.uhn.fhir.jpa.cache.ResourceVersionSvcDaoImpl;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class ResourceVersionSvcTest {
// helper class to package up data for helper methods
private class ResourceIdPackage {
public IIdType MyResourceId;
public ResourcePersistentId MyPid;
public Long MyVersion;
public ResourceIdPackage(IIdType id,
ResourcePersistentId pid,
Long version) {
MyResourceId = id;
MyPid = pid;
MyVersion = version;
}
}
@Mock
DaoRegistry myDaoRegistry;
@Mock
IResourceTableDao myResourceTableDao;
@Mock
IdHelperService myIdHelperService;
// TODO KHS move the methods that use this out to a separate test class
@InjectMocks
private ResourceVersionSvcDaoImpl myResourceVersionSvc;
/**
* Gets a ResourceTable record for getResourceVersionsForPid
* Order matters!
* @param resourceType
* @param pid
* @param version
* @return
*/
private Object[] getResourceTableRecordForResourceTypeAndPid(String resourceType, long pid, long version) {
return new Object[] {
pid, // long
resourceType, // string
version // long
};
}
/**
* Helper function to mock out resolveResourcePersistentIdsWithCache
* to return empty lists (as if no resources were found).
*/
private void mock_resolveResourcePersistentIdsWithCache_toReturnNothing() {
CriteriaBuilder cb = Mockito.mock(CriteriaBuilder.class);
CriteriaQuery<ForcedId> criteriaQuery = Mockito.mock(CriteriaQuery.class);
Root<ForcedId> from = Mockito.mock(Root.class);
Path path = Mockito.mock(Path.class);
TypedQuery<ForcedId> queryMock = Mockito.mock(TypedQuery.class);
}
/**
* Helper function to mock out getIdsOfExistingResources
* to return the matches and resources matching those provided
* by parameters.
* @param theResourcePacks
*/
private void mockReturnsFor_getIdsOfExistingResources(ResourceIdPackage... theResourcePacks) {
List<ResourcePersistentId> resourcePersistentIds = new ArrayList<>();
List<Object[]> matches = new ArrayList<>();
for (ResourceIdPackage pack : theResourcePacks) {
resourcePersistentIds.add(pack.MyPid);
matches.add(getResourceTableRecordForResourceTypeAndPid(
pack.MyResourceId.getResourceType(),
pack.MyPid.getIdAsLong(),
pack.MyVersion
));
}
ResourcePersistentId first = resourcePersistentIds.remove(0);
if (resourcePersistentIds.isEmpty()) {
when(myIdHelperService.resolveResourcePersistentIdsWithCache(any(), any())).thenReturn(Collections.singletonList(first));
}
else {
when(myIdHelperService.resolveResourcePersistentIdsWithCache(any(), any())).thenReturn(resourcePersistentIds);
}
}
@Test
public void getLatestVersionIdsForResourceIds_whenResourceExists_returnsMapWithPIDAndVersion() {
IIdType type = new IdDt("Patient/RED");
ResourcePersistentId pid = new ResourcePersistentId(1L);
pid.setAssociatedResourceId(type);
HashMap<IIdType, ResourcePersistentId> map = new HashMap<>();
map.put(type, pid);
ResourceIdPackage pack = new ResourceIdPackage(type, pid, 2L);
// when
mockReturnsFor_getIdsOfExistingResources(pack);
// test
ResourcePersistentIdMap retMap = myResourceVersionSvc.getLatestVersionIdsForResourceIds(RequestPartitionId.allPartitions(),
Collections.singletonList(type));
Assertions.assertTrue(retMap.containsKey(type));
Assertions.assertEquals(pid.getVersion(), map.get(type).getVersion());
}
@Test
public void getLatestVersionIdsForResourceIds_whenResourceDoesNotExist_returnsEmptyMap() {
IIdType type = new IdDt("Patient/RED");
// when
mock_resolveResourcePersistentIdsWithCache_toReturnNothing();
// test
ResourcePersistentIdMap retMap = myResourceVersionSvc.getLatestVersionIdsForResourceIds(RequestPartitionId.allPartitions(),
Collections.singletonList(type));
Assertions.assertTrue(retMap.isEmpty());
}
@Test
public void getLatestVersionIdsForResourceIds_whenSomeResourcesDoNotExist_returnsOnlyExistingElements() {
// resource to be found
IIdType type = new IdDt("Patient/RED");
ResourcePersistentId pid = new ResourcePersistentId(1L);
pid.setAssociatedResourceId(type);
ResourceIdPackage pack = new ResourceIdPackage(type, pid, 2L);
// resource that won't be found
IIdType type2 = new IdDt("Patient/BLUE");
// when
mock_resolveResourcePersistentIdsWithCache_toReturnNothing();
mockReturnsFor_getIdsOfExistingResources(pack);
// test
ResourcePersistentIdMap retMap = myResourceVersionSvc.getLatestVersionIdsForResourceIds(
RequestPartitionId.allPartitions(),
Arrays.asList(type, type2)
);
// verify
Assertions.assertEquals(1, retMap.size());
Assertions.assertTrue(retMap.containsKey(type));
Assertions.assertFalse(retMap.containsKey(type2));
}
@Test
public void testReplaceDefault_AllPartitions() {
IdHelperService svc = new IdHelperService();
PartitionSettings partitionSettings = new PartitionSettings();
partitionSettings.setDefaultPartitionId(1);
svc.setPartitionSettingsForUnitTest(partitionSettings);
RequestPartitionId outcome = svc.replaceDefault(RequestPartitionId.allPartitions());
assertSame(RequestPartitionId.allPartitions(), outcome);
}
@Test
public void testReplaceDefault_DefaultPartition() {
IdHelperService svc = new IdHelperService();
PartitionSettings partitionSettings = new PartitionSettings();
partitionSettings.setDefaultPartitionId(1);
svc.setPartitionSettingsForUnitTest(partitionSettings);
RequestPartitionId outcome = svc.replaceDefault(RequestPartitionId.defaultPartition());
assertEquals(1, outcome.getPartitionIds().get(0));
}
}

View File

@ -0,0 +1,388 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ChainingR4SearchTest extends BaseJpaR4Test {
@Autowired
MatchUrlService myMatchUrlService;
@AfterEach
public void after() throws Exception {
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
myDaoConfig.setCountSearchResultsUpTo(new DaoConfig().getCountSearchResultsUpTo());
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myModelConfig.setIndexOnContainedResources(false);
myModelConfig.setIndexOnContainedResources(new ModelConfig().isIndexOnContainedResources());
}
@BeforeEach
public void before() throws Exception {
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
myDaoConfig.setAllowMultipleDelete(true);
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
myModelConfig.setIndexOnContainedResources(true);
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Test
public void testShouldResolveATwoLinkChainWithStandAloneResources() throws Exception {
// setup
IIdType oid1;
{
Patient p = new Patient();
p.setId(IdType.newRandomUuid());
p.addName().setFamily("Smith").addGiven("John");
myPatientDao.create(p, mySrd);
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getSubject().setReference(p.getId());
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject.name=Smith";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
@Test
public void testShouldResolveATwoLinkChainWithAContainedResource() throws Exception {
// setup
IIdType oid1;
{
Patient p = new Patient();
p.setId("pat");
p.addName().setFamily("Smith").addGiven("John");
Observation obs = new Observation();
obs.getContained().add(p);
obs.getCode().setText("Observation 1");
obs.setValue(new StringType("Test"));
obs.getSubject().setReference("#pat");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject.name=Smith";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
@Test
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAlone() throws Exception {
// setup
IIdType oid1;
{
Organization org = new Organization();
org.setId(IdType.newRandomUuid());
org.setName("HealthCo");
myOrganizationDao.create(org, mySrd);
Patient p = new Patient();
p.setId(IdType.newRandomUuid());
p.addName().setFamily("Smith").addGiven("John");
p.getManagingOrganization().setReference(org.getId());
myPatientDao.create(p, mySrd);
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getSubject().setReference(p.getId());
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject.organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
@Test
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheChain() throws Exception {
// This is the case that is most relevant to SMILE-2899
// setup
IIdType oid1;
{
Organization org = new Organization();
org.setId("org");
org.setName("HealthCo");
Patient p = new Patient();
p.setId(IdType.newRandomUuid());
p.getContained().add(org);
p.addName().setFamily("Smith").addGiven("John");
p.getManagingOrganization().setReference("#org");
myPatientDao.create(p, mySrd);
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getSubject().setReference(p.getId());
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject.organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
@Test
@Disabled
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheBeginningOfTheChain() throws Exception {
// We do not currently support this case - we may not be indexing the references of contained resources
// setup
IIdType oid1;
{
Organization org = new Organization();
org.setId(IdType.newRandomUuid());
org.setName("HealthCo");
myOrganizationDao.create(org, mySrd);
Patient p = new Patient();
p.setId("pat");
p.addName().setFamily("Smith").addGiven("John");
p.getManagingOrganization().setReference(org.getId());
Observation obs = new Observation();
obs.getContained().add(p);
obs.getCode().setText("Observation 1");
obs.getSubject().setReference("#pat");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject.organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
@Test
public void testShouldResolveAThreeLinkChainWithQualifiersWhereAllResourcesStandAlone() throws Exception {
// setup
IIdType oid1;
{
Organization org = new Organization();
org.setId(IdType.newRandomUuid());
org.setName("HealthCo");
myOrganizationDao.create(org, mySrd);
Patient p = new Patient();
p.setId(IdType.newRandomUuid());
p.addName().setFamily("Smith").addGiven("John");
p.getManagingOrganization().setReference(org.getId());
myPatientDao.create(p, mySrd);
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getSubject().setReference(p.getId());
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
@Test
public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResourceAtTheEndOfTheChain() throws Exception {
// This is the case that is most relevant to SMILE-2899
// setup
IIdType oid1;
{
Organization org = new Organization();
org.setId("org");
org.setName("HealthCo");
Patient p = new Patient();
p.setId(IdType.newRandomUuid());
p.getContained().add(org);
p.addName().setFamily("Smith").addGiven("John");
p.getManagingOrganization().setReference("#org");
myPatientDao.create(p, mySrd);
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getSubject().setReference(p.getId());
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
@Test
public void testShouldResolveAFourLinkChainWhereAllResourcesStandAlone() throws Exception {
// setup
IIdType oid1;
{
Organization org = new Organization();
org.setId(IdType.newRandomUuid());
org.setName("HealthCo");
myOrganizationDao.create(org, mySrd);
Organization partOfOrg = new Organization();
partOfOrg.setId(IdType.newRandomUuid());
partOfOrg.getPartOf().setReference(org.getId());
myOrganizationDao.create(partOfOrg, mySrd);
Patient p = new Patient();
p.setId(IdType.newRandomUuid());
p.addName().setFamily("Smith").addGiven("John");
p.getManagingOrganization().setReference(partOfOrg.getId());
myPatientDao.create(p, mySrd);
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getSubject().setReference(p.getId());
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject.organization.partof.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
@Test
public void testShouldResolveAFourLinkChainWhereTheLastReferenceIsContained() throws Exception {
// setup
IIdType oid1;
{
Organization org = new Organization();
org.setId("parent");
org.setName("HealthCo");
Organization partOfOrg = new Organization();
partOfOrg.setId(IdType.newRandomUuid());
partOfOrg.getContained().add(org);
partOfOrg.getPartOf().setReference("#parent");
myOrganizationDao.create(partOfOrg, mySrd);
Patient p = new Patient();
p.setId(IdType.newRandomUuid());
p.addName().setFamily("Smith").addGiven("John");
p.getManagingOrganization().setReference(partOfOrg.getId());
myPatientDao.create(p, mySrd);
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getSubject().setReference(p.getId());
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String url = "/Observation?subject.organization.partof.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
// validate
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getIdPart()));
}
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String theUrl) throws IOException {
List<String> ids = new ArrayList<>();
ResourceSearch search = myMatchUrlService.getResourceSearch(theUrl);
SearchParameterMap map = search.getSearchParameterMap();
map.setLoadSynchronous(true);
IBundleProvider result = myObservationDao.search(map);
return result.getAllResourceIds();
}
}

View File

@ -0,0 +1,49 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.config.ElasticsearchWithPrefixConfig;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchRestClientFactory;
import ca.uhn.fhir.test.utilities.docker.RequiresDocker;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@RequiresDocker
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {ElasticsearchWithPrefixConfig.class})
public class ElasticsearchPrefixTest {
@Autowired
ElasticsearchContainer elasticsearchContainer;
public static String ELASTIC_PREFIX = "hapi-fhir";
@Test
public void test() throws IOException {
//Given
RestHighLevelClient elasticsearchHighLevelRestClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(
"http", elasticsearchContainer.getHost() + ":" + elasticsearchContainer.getMappedPort(9200), "", "");
//When
RestClient lowLevelClient = elasticsearchHighLevelRestClient.getLowLevelClient();
Response get = lowLevelClient.performRequest(new Request("GET", "/_cat/indices"));
String catIndexes = EntityUtils.toString(get.getEntity());
//Then
assertThat(catIndexes, containsString(ELASTIC_PREFIX + "-resourcetable-000001"));
assertThat(catIndexes, containsString(ELASTIC_PREFIX + "-termconcept-000001"));
}
}

View File

@ -1,17 +1,22 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.BundleBuilder;
import ca.uhn.fhir.util.HapiExtensions; import ca.uhn.fhir.util.HapiExtensions;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.AuditEvent; import org.hl7.fhir.r4.model.AuditEvent;
import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Identifier;
@ -21,6 +26,7 @@ import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Task; import org.hl7.fhir.r4.model.Task;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
@ -48,12 +54,11 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy()); myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy());
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new DaoConfig().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets()); myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new DaoConfig().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets());
myDaoConfig.setBundleTypesAllowedForStorage(new DaoConfig().getBundleTypesAllowedForStorage()); myDaoConfig.setBundleTypesAllowedForStorage(new DaoConfig().getBundleTypesAllowedForStorage());
myModelConfig.setAutoVersionReferenceAtPaths(new ModelConfig().getAutoVersionReferenceAtPaths());
} }
@Test @Test
public void testCreateWithBadReferenceFails() { public void testCreateWithBadReferenceFails() {
Observation o = new Observation(); Observation o = new Observation();
o.setStatus(ObservationStatus.FINAL); o.setStatus(ObservationStatus.FINAL);
o.getSubject().setReference("Patient/FOO"); o.getSubject().setReference("Patient/FOO");
@ -97,27 +102,23 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
params.add(Task.SP_PART_OF, new ReferenceParam("Task/AAA")); params.add(Task.SP_PART_OF, new ReferenceParam("Task/AAA"));
List<String> found = toUnqualifiedVersionlessIdValues(myTaskDao.search(params)); List<String> found = toUnqualifiedVersionlessIdValues(myTaskDao.search(params));
assertThat(found, contains(id.getValue())); assertThat(found, contains(id.getValue()));
} }
@Test @Test
public void testUpdateWithBadReferenceFails() { public void testUpdateWithBadReferenceFails() {
Observation o1 = new Observation();
o1.setStatus(ObservationStatus.FINAL);
IIdType id = myObservationDao.create(o1, mySrd).getId();
Observation o = new Observation(); Observation o = new Observation();
o.setStatus(ObservationStatus.FINAL);
IIdType id = myObservationDao.create(o, mySrd).getId();
o = new Observation();
o.setId(id); o.setId(id);
o.setStatus(ObservationStatus.FINAL); o.setStatus(ObservationStatus.FINAL);
o.getSubject().setReference("Patient/FOO"); o.getSubject().setReference("Patient/FOO");
try {
Exception ex = Assertions.assertThrows(InvalidRequestException.class, () -> {
myObservationDao.update(o, mySrd); myObservationDao.update(o, mySrd);
fail(); });
} catch (InvalidRequestException e) { assertThat(ex.getMessage(), startsWith("Resource Patient/FOO not found, specified in path: Observation.subject"));
assertThat(e.getMessage(), startsWith("Resource Patient/FOO not found, specified in path: Observation.subject"));
}
} }
@Test @Test
@ -450,8 +451,6 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
ourLog.info("\nObservation read after Patient update:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs)); ourLog.info("\nObservation read after Patient update:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
assertEquals(createdObs.getSubject().getReference(), conditionalUpdatePatId.toUnqualifiedVersionless().getValueAsString()); assertEquals(createdObs.getSubject().getReference(), conditionalUpdatePatId.toUnqualifiedVersionless().getValueAsString());
assertEquals(placeholderPatId.toUnqualifiedVersionless().getValueAsString(), conditionalUpdatePatId.toUnqualifiedVersionless().getValueAsString()); assertEquals(placeholderPatId.toUnqualifiedVersionless().getValueAsString(), conditionalUpdatePatId.toUnqualifiedVersionless().getValueAsString());
} }
@Test @Test
@ -540,7 +539,6 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
AuditEvent createdEvent = myAuditEventDao.read(id); AuditEvent createdEvent = myAuditEventDao.read(id);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEvent)); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEvent));
} }
@Test @Test
@ -560,11 +558,96 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
IIdType id = myObservationDao.create(obsToCreate, mySrd).getId(); IIdType id = myObservationDao.create(obsToCreate, mySrd).getId();
Observation createdObs = myObservationDao.read(id); Observation createdObs = myObservationDao.read(id);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
assertEquals("Patient/ABC", createdObs.getSubject().getReference()); assertEquals("Patient/ABC", createdObs.getSubject().getReference());
}
@Test
public void testAutocreatePlaceholderTest() {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
Observation obs = new Observation();
obs.setId("Observation/DEF");
Reference patientRef = new Reference("Patient/RED");
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirCtx);
builder.addTransactionUpdateEntry(obs);
mySystemDao.transaction(new SystemRequestDetails(), (Bundle) builder.getBundle());
// verify subresource is created
Patient returned = myPatientDao.read(patientRef.getReferenceElement());
assertNotNull(returned);
} }
@Test
public void testAutocreatePlaceholderWithTargetExistingAlreadyTest() {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
String patientId = "Patient/RED";
// create
Patient patient = new Patient();
patient.setIdElement(new IdType(patientId));
myPatientDao.update(patient); // use update to use forcedid
// update
patient.setActive(true);
myPatientDao.update(patient);
// observation (with version 2)
Observation obs = new Observation();
obs.setId("Observation/DEF");
Reference patientRef = new Reference(patientId);
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirCtx);
builder.addTransactionUpdateEntry(obs);
Bundle transaction = mySystemDao.transaction(new SystemRequestDetails(), (Bundle) builder.getBundle());
Patient returned = myPatientDao.read(patientRef.getReferenceElement());
assertNotNull(returned);
Assertions.assertTrue(returned.getActive());
Assertions.assertEquals(2, returned.getIdElement().getVersionIdPartAsLong());
Observation retObservation = myObservationDao.read(obs.getIdElement());
assertNotNull(retObservation);
}
/**
* This test is the same as above, except it uses the serverid (instead of forcedid)
*/
@Test
public void testAutocreatePlaceholderWithExistingTargetWithServerAssignedIdTest() {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
// create
Patient patient = new Patient();
patient.setIdElement(new IdType("Patient"));
DaoMethodOutcome ret = myPatientDao.create(patient); // use create to use server id
// update - to update our version
patient.setActive(true);
myPatientDao.update(patient);
// observation (with version 2)
Observation obs = new Observation();
obs.setId("Observation/DEF");
Reference patientRef = new Reference("Patient/" + ret.getId().getIdPart());
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirCtx);
builder.addTransactionUpdateEntry(obs);
Bundle transaction = mySystemDao.transaction(new SystemRequestDetails(), (Bundle) builder.getBundle());
Patient returned = myPatientDao.read(patientRef.getReferenceElement());
assertNotNull(returned);
Assertions.assertEquals(2, returned.getIdElement().getVersionIdPartAsLong());
Observation retObservation = myObservationDao.read(obs.getIdElement());
assertNotNull(retObservation);
}
} }

View File

@ -1,10 +1,10 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import static org.hamcrest.MatcherAssert.assertThat; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import static org.hamcrest.Matchers.containsInAnyOrder; import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
import static org.junit.jupiter.api.Assertions.assertEquals; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import static org.junit.jupiter.api.Assertions.fail; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Address; import org.hl7.fhir.r4.model.Address;
import org.hl7.fhir.r4.model.Address.AddressUse; import org.hl7.fhir.r4.model.Address.AddressUse;
@ -26,15 +26,13 @@ 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 ca.uhn.fhir.rest.api.SearchContainedModeEnum;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import java.util.stream.Collector;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test { public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ContainedTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ContainedTest.class);
@ -80,7 +78,6 @@ public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test {
map = new SearchParameterMap(); map = new SearchParameterMap();
map.add("subject", new ReferenceParam("name", "Smith")); map.add("subject", new ReferenceParam("name", "Smith"));
map.setSearchContainedMode(SearchContainedModeEnum.TRUE); map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id))); assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id)));
} }
@ -116,8 +113,10 @@ public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test {
map = new SearchParameterMap(); map = new SearchParameterMap();
map.add("subject", new ReferenceParam("name", "Smith")); map.add("subject", new ReferenceParam("name", "Smith"));
map.setSearchContainedMode(SearchContainedModeEnum.TRUE); map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
map.setLoadSynchronous(true);
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id))); assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id)));
} }

View File

@ -9,11 +9,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
import ca.uhn.fhir.test.utilities.docker.RequiresDocker; import ca.uhn.fhir.test.utilities.docker.RequiresDocker;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hibernate.search.backend.elasticsearch.index.IndexStatus;
import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
@ -28,6 +32,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
@ -341,6 +347,4 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
} }
} }

View File

@ -1,9 +1,11 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails; import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.BundleBuilder; import ca.uhn.fhir.util.BundleBuilder;
@ -20,8 +22,11 @@ import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Task; import org.hl7.fhir.r4.model.Task;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.InputStreamReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
@ -30,9 +35,12 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test { public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
@ -90,7 +98,6 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
assertEquals("Patient/A/_history/1", eob2.getPatient().getReference()); assertEquals("Patient/A/_history/1", eob2.getPatient().getReference());
} }
@Test @Test
public void testCreateAndUpdateVersionedReferencesInTransaction_VersionedReferenceToVersionedReferenceToUpsertWithNop() { public void testCreateAndUpdateVersionedReferencesInTransaction_VersionedReferenceToVersionedReferenceToUpsertWithNop() {
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false); myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
@ -283,7 +290,7 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
myObservationDao.update(observation); myObservationDao.update(observation);
// Make sure we're not introducing any extra DB operations // Make sure we're not introducing any extra DB operations
assertEquals(5, myCaptureQueriesListener.logSelectQueries().size()); assertEquals(6, myCaptureQueriesListener.logSelectQueries().size());
// Read back and verify that reference is now versioned // Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId); observation = myObservationDao.read(observationId);
@ -295,7 +302,6 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false); myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject"); myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
BundleBuilder builder = new BundleBuilder(myFhirCtx); BundleBuilder builder = new BundleBuilder(myFhirCtx);
Patient patient = new Patient(); Patient patient = new Patient();
@ -326,7 +332,6 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
observation = myObservationDao.read(observationId); observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference()); assertEquals(patientId.getValue(), observation.getSubject().getReference());
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference()); assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
} }
@Test @Test
@ -389,7 +394,6 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
assertEquals(patientId.getValue(), observation.getSubject().getReference()); assertEquals(patientId.getValue(), observation.getSubject().getReference());
assertEquals("2", observation.getSubject().getReferenceElement().getVersionIdPart()); assertEquals("2", observation.getSubject().getReferenceElement().getVersionIdPart());
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference()); assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
} }
@ -409,7 +413,6 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
// Update patient to make a second version // Update patient to make a second version
patient.setActive(false); patient.setActive(false);
myPatientDao.update(patient); myPatientDao.update(patient);
} }
BundleBuilder builder = new BundleBuilder(myFhirCtx); BundleBuilder builder = new BundleBuilder(myFhirCtx);
@ -458,7 +461,6 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
// Update patient to make a second version // Update patient to make a second version
patient.setActive(false); patient.setActive(false);
myPatientDao.update(patient); myPatientDao.update(patient);
} }
BundleBuilder builder = new BundleBuilder(myFhirCtx); BundleBuilder builder = new BundleBuilder(myFhirCtx);
@ -491,10 +493,8 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
// Read back and verify that reference is now versioned // Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId); observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference()); assertEquals(patientId.getValue(), observation.getSubject().getReference());
} }
@Test @Test
public void testSearchAndIncludeVersionedReference_Asynchronous() { public void testSearchAndIncludeVersionedReference_Asynchronous() {
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false); myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
@ -780,4 +780,120 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue()); assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
} }
} }
@Test
public void testNoNpeOnEoBBundle() {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
List<String> strings = Arrays.asList(
"ExplanationOfBenefit.patient",
"ExplanationOfBenefit.insurer",
"ExplanationOfBenefit.provider",
"ExplanationOfBenefit.careTeam.provider",
"ExplanationOfBenefit.insurance.coverage",
"ExplanationOfBenefit.payee.party"
);
myModelConfig.setAutoVersionReferenceAtPaths(new HashSet<>(strings));
Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class,
new InputStreamReader(
FhirResourceDaoR4VersionedReferenceTest.class.getResourceAsStream("/npe-causing-bundle.json")));
Bundle transaction = mySystemDao.transaction(new SystemRequestDetails(), bundle);
assertNotNull(transaction);
}
@Test
public void testAutoVersionPathsWithAutoCreatePlaceholders() {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
Observation obs = new Observation();
obs.setId("Observation/CDE");
obs.setSubject(new Reference("Patient/ABC"));
DaoMethodOutcome update = myObservationDao.create(obs);
Observation resource = (Observation)update.getResource();
String versionedPatientReference = resource.getSubject().getReference();
assertThat(versionedPatientReference, is(equalTo("Patient/ABC")));
Patient p = myPatientDao.read(new IdDt("Patient/ABC"));
Assertions.assertNotNull(p);
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
obs = new Observation();
obs.setId("Observation/DEF");
obs.setSubject(new Reference("Patient/RED"));
update = myObservationDao.create(obs);
resource = (Observation)update.getResource();
versionedPatientReference = resource.getSubject().getReference();
assertThat(versionedPatientReference, is(equalTo("Patient/RED/_history/1")));
}
@Test
@DisplayName("Bundle transaction with AutoVersionReferenceAtPath on and with existing Patient resource should create")
public void bundleTransaction_autoreferenceAtPathWithPreexistingPatientReference_shouldCreate() {
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
String patientId = "Patient/RED";
IIdType idType = new IdDt(patientId);
// create patient ahead of time
Patient patient = new Patient();
patient.setId(patientId);
DaoMethodOutcome outcome = myPatientDao.update(patient);
assertThat(outcome.getResource().getIdElement().getValue(), is(equalTo(patientId + "/_history/1")));
Patient returned = myPatientDao.read(idType);
Assertions.assertNotNull(returned);
assertThat(returned.getId(), is(equalTo(patientId + "/_history/1")));
// update to change version
patient.setActive(true);
myPatientDao.update(patient);
Observation obs = new Observation();
obs.setId("Observation/DEF");
Reference patientRef = new Reference(patientId);
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirCtx);
builder.addTransactionUpdateEntry(obs);
Bundle submitted = (Bundle)builder.getBundle();
Bundle returnedTr = mySystemDao.transaction(new SystemRequestDetails(), submitted);
Assertions.assertNotNull(returnedTr);
// some verification
Observation obRet = myObservationDao.read(obs.getIdElement());
Assertions.assertNotNull(obRet);
}
@Test
@DisplayName("GH-2901 Test no NPE is thrown on autoversioned references")
public void testNoNpeMinimal() {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
Observation obs = new Observation();
obs.setId("Observation/DEF");
Reference patientRef = new Reference("Patient/RED");
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirCtx);
builder.addTransactionUpdateEntry(obs);
Bundle submitted = (Bundle)builder.getBundle();
Bundle returnedTr = mySystemDao.transaction(new SystemRequestDetails(), submitted);
Assertions.assertNotNull(returnedTr);
// some verification
Observation obRet = myObservationDao.read(obs.getIdElement());
Assertions.assertNotNull(obRet);
Patient returned = myPatientDao.read(patientRef.getReferenceElement());
Assertions.assertNotNull(returned);
}
} }

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel; import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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.TagTypeEnum; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@ -119,6 +120,8 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED); myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
myDaoConfig.setBundleBatchPoolSize(new DaoConfig().getBundleBatchPoolSize()); myDaoConfig.setBundleBatchPoolSize(new DaoConfig().getBundleBatchPoolSize());
myDaoConfig.setBundleBatchMaxPoolSize(new DaoConfig().getBundleBatchMaxPoolSize()); myDaoConfig.setBundleBatchMaxPoolSize(new DaoConfig().getBundleBatchMaxPoolSize());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
myModelConfig.setAutoVersionReferenceAtPaths(new ModelConfig().getAutoVersionReferenceAtPaths());
} }
@BeforeEach @BeforeEach

View File

@ -250,6 +250,45 @@ public class ResourceProviderR4BundleTest extends BaseResourceProviderR4Test {
} }
@Test
public void testTagCacheWorksWithBatchMode() {
Bundle input = new Bundle();
input.setType(BundleType.BATCH);
Patient p = new Patient();
p.setId("100");
p.setGender(AdministrativeGender.MALE);
p.addIdentifier().setSystem("urn:foo").setValue("A");
p.addName().setFamily("Smith");
p.getMeta().addTag().setSystem("mysystem").setCode("mycode");
input.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST);
Patient p2 = new Patient();
p2.setId("200");
p2.setGender(AdministrativeGender.MALE);
p2.addIdentifier().setSystem("urn:foo").setValue("A");
p2.addName().setFamily("Smith");
p2.getMeta().addTag().setSystem("mysystem").setCode("mycode");
input.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST);
Patient p3 = new Patient();
p3.setId("pat-300");
p3.setGender(AdministrativeGender.MALE);
p3.addIdentifier().setSystem("urn:foo").setValue("A");
p3.addName().setFamily("Smith");
p3.getMeta().addTag().setSystem("mysystem").setCode("mycode");
input.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient/pat-300");
Bundle output = myClient.transaction().withBundle(input).execute();
output.getEntry().stream()
.map(BundleEntryComponent::getResponse)
.map(Bundle.BundleEntryResponseComponent::getStatus)
.forEach(statusCode -> {
assertEquals(statusCode, "201 Created");
});
}
private List<String> createPatients(int count) { private List<String> createPatients(int count) {
List<String> ids = new ArrayList<String>(); List<String> ids = new ArrayList<String>();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {

View File

@ -23,17 +23,11 @@ class ElasticsearchHibernatePropertiesBuilderTest {
ElasticsearchHibernatePropertiesBuilder myPropertiesBuilder = spy(ElasticsearchHibernatePropertiesBuilder.class); ElasticsearchHibernatePropertiesBuilder myPropertiesBuilder = spy(ElasticsearchHibernatePropertiesBuilder.class);
@BeforeEach
public void prepMocks() {
//ensures we don't try to reach out to a real ES server on apply.
doNothing().when(myPropertiesBuilder).injectStartupTemplate(any(), any(), any(), any());
}
@Test @Test
public void testRestUrlCannotContainProtocol() { public void testHostsCannotContainProtocol() {
String host = "localhost:9200"; String host = "localhost:9200";
String protocolHost = "https://" + host; String protocolHost = "https://" + host;
String failureMessage = "Elasticsearch URL cannot include a protocol, that is a separate property. Remove http:// or https:// from this URL."; String failureMessage = "Elasticsearch URLs cannot include a protocol, that is a separate property. Remove http:// or https:// from this URL.";
myPropertiesBuilder myPropertiesBuilder
.setProtocol("https") .setProtocol("https")
@ -42,19 +36,42 @@ class ElasticsearchHibernatePropertiesBuilderTest {
//SUT //SUT
try { try {
myPropertiesBuilder.setRestUrl(protocolHost); myPropertiesBuilder.setHosts(protocolHost)
.apply(new Properties());
fail(); fail();
} catch (ConfigurationException e ) { } catch (ConfigurationException e ) {
assertThat(e.getMessage(), is(equalTo(failureMessage))); assertThat(e.getMessage(), is(equalTo(failureMessage)));
} }
doNothing().when(myPropertiesBuilder).injectStartupTemplate(any(), any(), any(), any());
Properties properties = new Properties(); Properties properties = new Properties();
myPropertiesBuilder myPropertiesBuilder
.setRestUrl(host) .setHosts(host)
.apply(properties); .apply(properties);
assertThat(properties.getProperty(BackendSettings.backendKey(ElasticsearchBackendSettings.HOSTS)), is(equalTo(host))); assertThat(properties.getProperty(BackendSettings.backendKey(ElasticsearchBackendSettings.HOSTS)), is(equalTo(host)));
} }
@Test
public void testHostsValueValidation() {
String host = "localhost_9200,localhost:9201,localhost:9202";
String failureMessage = "Elasticsearch URLs have to contain ':' as a host:port separator. Example: localhost:9200,localhost:9201,localhost:9202";
myPropertiesBuilder
.setProtocol("https")
.setHosts(host)
.setUsername("whatever")
.setPassword("whatever");
//SUT
try {
myPropertiesBuilder
.apply(new Properties());
fail();
} catch (ConfigurationException e ) {
assertThat(e.getMessage(), is(equalTo(failureMessage)));
}
}
} }

View File

@ -68,7 +68,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
public void before() throws IOException { public void before() throws IOException {
PartitionSettings partitionSettings = new PartitionSettings(); PartitionSettings partitionSettings = new PartitionSettings();
partitionSettings.setPartitioningEnabled(false); partitionSettings.setPartitioningEnabled(false);
elasticsearchSvc = new ElasticsearchSvcImpl(partitionSettings, elasticsearchContainer.getHost(), elasticsearchContainer.getMappedPort(9200), "", ""); elasticsearchSvc = new ElasticsearchSvcImpl(partitionSettings, elasticsearchContainer.getHost() + ":" + elasticsearchContainer.getMappedPort(9200), null, null);
if (!indexLoaded) { if (!indexLoaded) {
createMultiplePatientsAndObservations(); createMultiplePatientsAndObservations();

View File

@ -97,7 +97,7 @@ public class LastNElasticsearchSvcSingleObservationIT {
public void before() { public void before() {
PartitionSettings partitionSettings = new PartitionSettings(); PartitionSettings partitionSettings = new PartitionSettings();
partitionSettings.setPartitioningEnabled(false); partitionSettings.setPartitioningEnabled(false);
elasticsearchSvc = new ElasticsearchSvcImpl(partitionSettings, elasticsearchContainer.getHost(), elasticsearchContainer.getMappedPort(9200), "", ""); elasticsearchSvc = new ElasticsearchSvcImpl(partitionSettings, elasticsearchContainer.getHost() + ":" + elasticsearchContainer.getMappedPort(9200), "", "");
} }
@AfterEach @AfterEach

View File

@ -14,6 +14,7 @@ import ca.uhn.fhir.jpa.cache.ResourceChangeListenerCache;
import ca.uhn.fhir.jpa.cache.ResourceChangeListenerCacheFactory; import ca.uhn.fhir.jpa.cache.ResourceChangeListenerCacheFactory;
import ca.uhn.fhir.jpa.cache.ResourceChangeListenerCacheRefresherImpl; import ca.uhn.fhir.jpa.cache.ResourceChangeListenerCacheRefresherImpl;
import ca.uhn.fhir.jpa.cache.ResourceChangeListenerRegistryImpl; import ca.uhn.fhir.jpa.cache.ResourceChangeListenerRegistryImpl;
import ca.uhn.fhir.jpa.cache.ResourcePersistentIdMap;
import ca.uhn.fhir.jpa.cache.ResourceVersionMap; import ca.uhn.fhir.jpa.cache.ResourceVersionMap;
import ca.uhn.fhir.jpa.dao.JpaResourceDao; import ca.uhn.fhir.jpa.dao.JpaResourceDao;
import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.TransactionProcessor;
@ -45,6 +46,7 @@ import com.google.common.collect.Lists;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hibernate.internal.SessionImpl; import org.hibernate.internal.SessionImpl;
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.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ExplanationOfBenefit; import org.hl7.fhir.r4.model.ExplanationOfBenefit;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -323,10 +325,15 @@ public class GiantTransactionPerfTest {
@Nonnull @Nonnull
@Override @Override
public ResourceVersionMap getVersionMap(String theResourceName, SearchParameterMap theSearchParamMap) { public ResourceVersionMap getVersionMap(RequestPartitionId theRequestPartitionId, String theResourceName, SearchParameterMap theSearchParamMap) {
myGetVersionMap++; myGetVersionMap++;
return ResourceVersionMap.fromResources(Lists.newArrayList()); return ResourceVersionMap.fromResources(Lists.newArrayList());
} }
@Override
public ResourcePersistentIdMap getLatestVersionIdsForResourceIds(RequestPartitionId thePartition, List<IIdType> theIds) {
return null;
}
} }
private class MockResourceHistoryTableDao implements IResourceHistoryTableDao { private class MockResourceHistoryTableDao implements IResourceHistoryTableDao {

View File

@ -0,0 +1,402 @@
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [ {
"resource": {
"resourceType": "ExplanationOfBenefit",
"id": "26d4cebd-95c6-39ea-855c-dc819bc68d08",
"meta": {
"lastUpdated": "2016-01-01T00:56:00.000-05:00",
"profile": [ "http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Inpatient-Institutional" ]
},
"identifier": [ {
"type": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "uc"
} ]
},
"system": "fhir/CodeSystem/sid/eob-inpatient-claim-id",
"value": "20550047"
} ],
"status": "active",
"type": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
"code": "institutional"
} ]
},
"use": "claim",
"patient": {
"reference": "Patient/ABC"
},
"billablePeriod": {
"start": "2015-12-14T00:00:00-05:00"
},
"created": "2021-08-16T13:54:10-04:00",
"insurer": {
"reference": "Organization/A"
},
"provider": {
"reference": "Organization/b9d22776-1ee9-3843-bc48-b4bf67861483"
},
"outcome": "complete",
"careTeam": [ {
"sequence": 1,
"provider": {
"reference": "Practitioner/H"
},
"role": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/claimcareteamrole",
"code": "primary"
} ]
}
}, {
"sequence": 2,
"provider": {
"reference": "Practitioner/I"
},
"role": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
"code": "attending"
} ]
}
}, {
"sequence": 3,
"provider": {
"reference": "Practitioner/J"
},
"role": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
"code": "performing"
} ]
}
} ],
"supportingInfo": [ {
"sequence": 1,
"category": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "admissionperiod"
} ]
},
"timingPeriod": {
"start": "2015-12-14T00:00:00-05:00",
"end": "2016-01-11T14:11:00-05:00"
}
}, {
"sequence": 2,
"category": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "clmrecvddate"
} ]
},
"timingDate": "2018-12-23"
}, {
"sequence": 3,
"category": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "admtype"
} ]
},
"code": {
"coding": [ {
"system": "https://www.nubc.org/CodeSystem/PriorityTypeOfAdmitOrVisit",
"code": "4"
} ]
}
} ],
"diagnosis": [ {
"sequence": 1,
"diagnosisCodeableConcept": {
"coding": [ {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z38.00"
} ]
},
"type": [ {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
} ]
} ]
} ],
"insurance": [ {
"focal": true,
"coverage": {
"reference": "Coverage/G"
}
} ],
"total": [ {
"category": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "benefit"
} ]
},
"amount": {
"value": 1039.28
}
}, {
"category": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "submitted"
} ]
},
"amount": {
"value": 2011
}
} ]
},
"request": {
"method": "PUT",
"url": "ExplanationOfBenefit/26d4cebd-95c6-39ea-855c-dc819bc68d08"
}
}, {
"resource": {
"resourceType": "ExplanationOfBenefit",
"id": "a25f1c3a-09b9-3f17-8f1b-0fbbf6391fce",
"meta": {
"lastUpdated": "2016-01-01T00:58:00.000-05:00",
"profile": [ "http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Inpatient-Institutional" ]
},
"identifier": [ {
"type": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "uc"
} ]
},
"system": "fhir/CodeSystem/sid/eob-inpatient-claim-id",
"value": "20586901"
} ],
"status": "active",
"type": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
"code": "institutional"
} ]
},
"use": "claim",
"patient": {
"reference": "Patient/ABC"
},
"billablePeriod": {
"start": "2015-12-18T00:00:00-05:00"
},
"created": "2021-08-16T13:54:10-04:00",
"insurer": {
"reference": "Organization/A"
},
"provider": {
"reference": "Organization/d10823cf-ee15-3a0e-a12e-1509cd18cda4"
},
"outcome": "complete",
"careTeam": [ {
"sequence": 1,
"provider": {
"reference": "Practitioner/D"
},
"role": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/claimcareteamrole",
"code": "primary"
} ]
}
}, {
"sequence": 2,
"provider": {
"reference": "Practitioner/E"
},
"role": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
"code": "attending"
} ]
}
}, {
"sequence": 3,
"provider": {
"reference": "Practitioner/F"
},
"role": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
"code": "performing"
} ]
}
} ],
"supportingInfo": [ {
"sequence": 1,
"category": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "admissionperiod"
} ]
},
"timingPeriod": {
"start": "2015-12-18T00:00:00-05:00",
"end": "2016-01-11T14:12:00-05:00"
}
}, {
"sequence": 2,
"category": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "clmrecvddate"
} ]
},
"timingDate": "2018-12-29"
}, {
"sequence": 3,
"category": {
"coding": [ {
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "admtype"
} ]
},
"code": {
"coding": [ {
"system": "https://www.nubc.org/CodeSystem/PriorityTypeOfAdmitOrVisit",
"code": "4"
} ]
}
} ],
"diagnosis": [ {
"sequence": 1,
"diagnosisCodeableConcept": {
"coding": [ {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z38.00"
} ]
},
"type": [ {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
} ]
} ]
}, {
"sequence": 2,
"diagnosisCodeableConcept": {
"coding": [ {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "P96.89"
} ]
},
"type": [ {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
} ]
} ]
}, {
"sequence": 3,
"diagnosisCodeableConcept": {
"coding": [ {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "R25.8"
} ]
},
"type": [ {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
} ]
} ]
}, {
"sequence": 4,
"diagnosisCodeableConcept": {
"coding": [ {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "P08.21"
} ]
},
"type": [ {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
} ]
} ]
}, {
"sequence": 5,
"diagnosisCodeableConcept": {
"coding": [ {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "P92.5"
} ]
},
"type": [ {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
} ]
} ]
}, {
"sequence": 6,
"diagnosisCodeableConcept": {
"coding": [ {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "P92.6"
} ]
},
"type": [ {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
} ]
} ]
}, {
"sequence": 7,
"diagnosisCodeableConcept": {
"coding": [ {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z23"
} ]
},
"type": [ {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
} ]
} ]
} ],
"insurance": [ {
"focal": true,
"coverage": {
"reference": "Coverage/G"
}
} ],
"total": [ {
"category": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "benefit"
} ]
},
"amount": {
"value": 1421.31
}
}, {
"category": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "submitted"
} ]
},
"amount": {
"value": 2336
}
} ]
},
"request": {
"method": "PUT",
"url": "ExplanationOfBenefit/a25f1c3a-09b9-3f17-8f1b-0fbbf6391fce"
}
} ] }

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -20,9 +20,12 @@ package ca.uhn.fhir.jpa.cache;
* #L% * #L%
*/ */
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
/** /**
* This interface is used by the {@link IResourceChangeListenerCacheRefresher} to read resources matching the provided * This interface is used by the {@link IResourceChangeListenerCacheRefresher} to read resources matching the provided
@ -30,5 +33,12 @@ import javax.annotation.Nonnull;
*/ */
public interface IResourceVersionSvc { public interface IResourceVersionSvc {
@Nonnull @Nonnull
ResourceVersionMap getVersionMap(String theResourceName, SearchParameterMap theSearchParamMap); ResourceVersionMap getVersionMap(RequestPartitionId theRequestPartitionId, String theResourceName, SearchParameterMap theSearchParamMap);
@Nonnull
default ResourceVersionMap getVersionMap(String theResourceName, SearchParameterMap theSearchParamMap) {
return getVersionMap(RequestPartitionId.allPartitions(), theResourceName, theSearchParamMap);
}
ResourcePersistentIdMap getLatestVersionIdsForResourceIds(RequestPartitionId thePartition, List<IIdType> theIds);
} }

View File

@ -173,8 +173,8 @@ public class ResourceChangeListenerCacheRefresherImpl implements IResourceChange
List<IIdType> updatedIds = new ArrayList<>(); List<IIdType> updatedIds = new ArrayList<>();
for (IIdType id : theNewResourceVersionMap.keySet()) { for (IIdType id : theNewResourceVersionMap.keySet()) {
String previousValue = theOldResourceVersionCache.put(id, theNewResourceVersionMap.get(id)); Long previousValue = theOldResourceVersionCache.put(id, theNewResourceVersionMap.get(id));
IIdType newId = id.withVersion(theNewResourceVersionMap.get(id)); IIdType newId = id.withVersion(theNewResourceVersionMap.get(id).toString());
if (previousValue == null) { if (previousValue == null) {
createdIds.add(newId); createdIds.add(newId);
} else if (!theNewResourceVersionMap.get(id).equals(previousValue)) { } else if (!theNewResourceVersionMap.get(id).equals(previousValue)) {

View File

@ -0,0 +1,67 @@
package ca.uhn.fhir.jpa.cache;
/*-
* #%L
* HAPI FHIR Search Parameters
* %%
* Copyright (C) 2014 - 2021 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.rest.api.server.storage.ResourcePersistentId;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ResourcePersistentIdMap {
private final Map<IIdType, ResourcePersistentId> myMap = new HashMap<>();
public static ResourcePersistentIdMap fromResourcePersistentIds(List<ResourcePersistentId> theResourcePersistentIds) {
ResourcePersistentIdMap retval = new ResourcePersistentIdMap();
theResourcePersistentIds.forEach(retval::add);
return retval;
}
private void add(ResourcePersistentId theResourcePersistentId) {
IIdType id = theResourcePersistentId.getAssociatedResourceId();
myMap.put(id.toUnqualifiedVersionless(), theResourcePersistentId);
}
public boolean containsKey(IIdType theId) {
return myMap.containsKey(theId.toUnqualifiedVersionless());
}
public ResourcePersistentId getResourcePersistentId(IIdType theId) {
return myMap.get(theId.toUnqualifiedVersionless());
}
public boolean isEmpty() {
return myMap.isEmpty();
}
public int size() {
return myMap.size();
}
public void put(IIdType theId, ResourcePersistentId thePid) {
myMap.put(theId, thePid);
}
public void putAll(ResourcePersistentIdMap theIdAndPID) {
myMap.putAll(theIdAndPID.myMap);
}
}

View File

@ -32,7 +32,7 @@ import java.util.Set;
* detect resources that were modified on remote servers in our cluster. * detect resources that were modified on remote servers in our cluster.
*/ */
public class ResourceVersionCache { public class ResourceVersionCache {
private final Map<IIdType, String> myVersionMap = new HashMap<>(); private final Map<IIdType, Long> myVersionMap = new HashMap<>();
public void clear() { public void clear() {
myVersionMap.clear(); myVersionMap.clear();
@ -43,15 +43,15 @@ public class ResourceVersionCache {
* @param theVersion * @param theVersion
* @return previous value * @return previous value
*/ */
public String put(IIdType theResourceId, String theVersion) { public Long put(IIdType theResourceId, Long theVersion) {
return myVersionMap.put(new IdDt(theResourceId).toVersionless(), theVersion); return myVersionMap.put(new IdDt(theResourceId).toVersionless(), theVersion);
} }
public String getVersionForResourceId(IIdType theResourceId) { public Long getVersionForResourceId(IIdType theResourceId) {
return myVersionMap.get(new IdDt(theResourceId)); return myVersionMap.get(new IdDt(theResourceId));
} }
public String removeResourceId(IIdType theResourceId) { public Long removeResourceId(IIdType theResourceId) {
return myVersionMap.remove(new IdDt(theResourceId)); return myVersionMap.remove(new IdDt(theResourceId));
} }

View File

@ -24,6 +24,8 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -36,8 +38,10 @@ import java.util.Set;
* This immutable map holds a copy of current resource versions read from the repository. * This immutable map holds a copy of current resource versions read from the repository.
*/ */
public class ResourceVersionMap { public class ResourceVersionMap {
private static final Logger ourLog = LoggerFactory.getLogger(ResourceVersionMap.class);
private final Set<IIdType> mySourceIds = new HashSet<>(); private final Set<IIdType> mySourceIds = new HashSet<>();
private final Map<IIdType, String> myMap = new HashMap<>(); // Key versionless id, value version
private final Map<IIdType, Long> myMap = new HashMap<>();
private ResourceVersionMap() {} private ResourceVersionMap() {}
public static ResourceVersionMap fromResourceTableEntities(List<ResourceTable> theEntities) { public static ResourceVersionMap fromResourceTableEntities(List<ResourceTable> theEntities) {
@ -57,13 +61,17 @@ public class ResourceVersionMap {
} }
private void add(IIdType theId) { private void add(IIdType theId) {
if (theId.getVersionIdPart() == null) {
ourLog.warn("Not storing {} in ResourceVersionMap because it does not have a version.", theId);
return;
}
IdDt id = new IdDt(theId); IdDt id = new IdDt(theId);
mySourceIds.add(id); mySourceIds.add(id);
myMap.put(id.toUnqualifiedVersionless(), id.getVersionIdPart()); myMap.put(id.toUnqualifiedVersionless(), id.getVersionIdPartAsLong());
} }
public String getVersion(IIdType theResourceId) { public Long getVersion(IIdType theResourceId) {
return myMap.get(new IdDt(theResourceId.toUnqualifiedVersionless())); return get(theResourceId);
} }
public int size() { public int size() {
@ -78,11 +86,15 @@ public class ResourceVersionMap {
return Collections.unmodifiableSet(mySourceIds); return Collections.unmodifiableSet(mySourceIds);
} }
public String get(IIdType theId) { public Long get(IIdType theId) {
return myMap.get(new IdDt(theId.toUnqualifiedVersionless())); return myMap.get(new IdDt(theId.toUnqualifiedVersionless()));
} }
public boolean containsKey(IIdType theId) { public boolean containsKey(IIdType theId) {
return myMap.containsKey(new IdDt(theId.toUnqualifiedVersionless())); return myMap.containsKey(new IdDt(theId.toUnqualifiedVersionless()));
} }
public boolean isEmpty() {
return myMap.isEmpty();
}
} }

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -41,6 +41,7 @@ import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
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.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -94,8 +95,8 @@ public class TestJpaDstu3Config extends BaseJavaConfigDstu3 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu3"); retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu3");
retVal.setDataSource(dataSource()); retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties()); retVal.setJpaProperties(jpaProperties());

View File

@ -41,6 +41,7 @@ import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
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.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -94,8 +95,8 @@ public class TestJpaR4Config extends BaseJavaConfigR4 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("PU_HapiFhirJpaR4"); retVal.setPersistenceUnitName("PU_HapiFhirJpaR4");
retVal.setDataSource(dataSource()); retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties()); retVal.setJpaProperties(jpaProperties());

View File

@ -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>5.6.0-PRE3-SNAPSHOT</version> <version>5.6.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -23,6 +23,7 @@ import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.hl7.fhir.dstu2.model.Subscription; import org.hl7.fhir.dstu2.model.Subscription;
import org.hl7.fhir.r5.utils.IResourceValidator; import org.hl7.fhir.r5.utils.IResourceValidator;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -134,8 +135,8 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
@Override @Override
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory(ConfigurableListableBeanFactory theConfigurableListableBeanFactory) {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(theConfigurableListableBeanFactory);
retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu2"); retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu2");
retVal.setDataSource(dataSource()); retVal.setDataSource(dataSource());
retVal.setJpaProperties(jpaProperties()); retVal.setJpaProperties(jpaProperties());

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