Replacing Hibernate Search 5 with Hibernate Search 6 (#2190)

* Beginning work on converting to hibernate search 6
* replace all out of date properties
* replace all POM references to old versions
* remove the shaded ES jars as HS6 supports 7.9
* Convert "Bridges" to RoutingBinder and RoutingBridge
* Modernize all indexing annotations
* Begin refactoring of search queries (wip)

* Fix spatial API changes, work on BaseTermReadSvcImpl

* Most of the way through the various filter property conversions

* Finished i think with BaseTermReadSvcImpl

* Compiling, but definitely broken

* Start test compilation failures

* All tests compiling (i think)

* remove suggest keywords operation

* Fix bootstrap errors, still have to deal with transient fields

* Rollback CR

* More refactoring, got IDs baked back into docs

* Fix coord util, fix a few more fields which require projection

* merge fixes

* begin refactor of valuesetexpansion tests

* fix another test

* Update tests

* remove todos, add TestContainers

* Fix descendant in

* Add testcontainers, fix another test

* fix more tests, replace embedded es

* Rip out embedded elastic, replace with TestContainer

* merge conflicts for new analyzers

* Bump to CR2 for hibernate search 6. Add forgotten CLI dep. Add backwards compatible codecs

* Remove dead deps, update testcontainer

* Remove java-hamcrest

* Pull asserts up, refactor tests to pass in RP tests

* Update V2 check

* Remove suggest keyword tests

* Split line for testing

* Fix perhaps one of the dumbest programming mistakes in my career so far

* Refactor tests which no longer rely on a strict known order

* Fix up test config, disable log test temporarily

* Remove log4j from being transitively pulled in. re-enable test

* Update log4j exclusions with new bridge. Update test to check for core class

* Move dependency to root pom in dep management section

* Update changelog, add changelog for HS6 with ES requirements. Remove dead comment

* update testst, remove todos

* Add hibernate props provider method, add elastic regexp query

* Add todo for high level client builder

* Modify ElasticsearchRestClientFactory to support HTTPS hosts

* Remove protocol extraction from rest url

* do we even need to index this...?

* remove purge of non-indexed resources

* Add longer timeout to testcontainers

* Add task to add docker CLI to azure pipeline via task, for testcontainers

* WIP remove this

* Dont update version

* Add test for duplicate termconcepts

* Use real hibernate ORM methods

* merge issues

* Add partitionsettigns bean to match master

* Trying to debug testcontainers on azure...

* Trying to debug testcontainers on azure...

* Run even on previous failure

* Fix testcontainer port binds

* Add full text logs attaching, and test reporting

* I love yaml

* Remove comment

* Refactor delta remove to not do a once-over traversal and flatten all children pre-delete. Resolves transaction commit boundary issue

* Modify TX beheaviour

* wip

* Rework to use config and mocked beans for partition-aware lastN

* rework to not use beans and just inject vars i need for test, keps test envs cleaner

* update azure pipeline to only copy test results

* Remove erroneous publish

* Always get test logs

* Make it so indexing works,

* Add todos

* revert deleteByPid

* Work on test fixes

* Test fixes

* Test fixes

* Another test fix

* Test fix

* Work on tests

* Test fixes

* All tests passing locally

* Fix test failure

* FIx build error

* Rename usages of HibernateDialectProvider -> HibernatePropertiesProvider

* Update ngram of token filters

* Tidying

* subvert the purpose of hibernate elastic props builder to inject template

* dead space

* Rename changed class

Co-authored-by: jamesagnew <jamesagnew@gmail.com>
This commit is contained in:
Tadgh 2021-01-05 17:56:59 -05:00 committed by GitHub
parent d943260927
commit efc6809ea6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
144 changed files with 2708 additions and 3868 deletions

View File

@ -17,6 +17,11 @@ jobs:
timeoutInMinutes: 360
container: maven:3-jdk-11
steps:
- task: DockerInstaller@0
displayName: Docker Installer
inputs:
dockerVersion: 17.09.0-ce
releaseType: stable
- task: Cache@2
inputs:
key: 'maven | "$(Agent.OS)" | ./pom.xml'
@ -35,8 +40,24 @@ jobs:
# These are JVM options (and don't show up in the build logs)
mavenOptions: '-Xmx1024m $(MAVEN_OPTS) -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS -Duser.timezone=America/Toronto'
jdkVersionOption: 1.11
- task: CopyFiles@2
condition: always()
inputs:
sourceFolder: '$(System.DefaultWorkingDirectory)/'
contents: '**/target/*output.txt'
targetFolder: '$(Build.ArtifactStagingDirectory)'
- task: PublishPipelineArtifact@1
displayName: 'Publish Full Test Output'
condition: always()
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/'
artifactName: 'full_logs.zip'
- script: bash <(curl https://codecov.io/bash) -t $(CODECOV_TOKEN)
displayName: 'codecov'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/TEST-*.xml'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: 'JaCoCo'

View File

@ -144,6 +144,7 @@ public class StringClientParam extends BaseClientParam implements IParam {
return new StringCriterion<>(getParamName(), theValue);
}
@Override
public ICriterion<StringClientParam> value(IPrimitiveType<String> theValue) {
return new StringCriterion<>(getParamName(), theValue.getValue());

View File

@ -254,8 +254,13 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.rauschig</groupId>
<artifactId>jarchivelib</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</dependencies>
<build>
<plugins>

View File

@ -23,10 +23,14 @@ package ca.uhn.fhir.jpa.demo;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -81,6 +85,8 @@ public class CommonConfig {
@Bean
public Properties jpaProperties() {
Properties extraProperties = new Properties();
//Regular Hibernate Settings
extraProperties.put("hibernate.dialect", H2Dialect.class.getName());
extraProperties.put("hibernate.format_sql", "true");
extraProperties.put("hibernate.show_sql", "false");
@ -90,14 +96,13 @@ public class CommonConfig {
extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.default.worker.execution", "async");
extraProperties.put(BackendSettings.backendKey(LuceneBackendSettings.ANALYSIS_CONFIGURER), HapiLuceneAnalysisConfigurer.class.getName());
extraProperties.put(BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_TYPE), "local-filesystem");
extraProperties.put(BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_ROOT), "target/lucenefiles");
extraProperties.put(BackendSettings.backendKey(LuceneBackendSettings.LUCENE_VERSION), "LUCENE_CURRENT");
if (System.getProperty("lowmem") != null) {
extraProperties.put("hibernate.search.autoregister_listeners", "false");
extraProperties.put(HibernateOrmMapperSettings.ENABLED, "false");
}
return extraProperties;

View File

@ -0,0 +1,5 @@
---
type: change
issue: 2190
title: "Updates to Hibernate Search require a full reindexing of all indexed fulltext data, which is held in Lucene or Elasticsearch.
Users using elasticsearch for fulltext indexing must upgrade to Elasticsearch 7.10.0."

View File

@ -6,9 +6,13 @@
<ul>
<li>SLF4j (All Modules): 1.7.28 -&gt; 1.7.30</li>
<li>Woodstox (XML FHIR Parser): 4.4.1 -&gt; 6.2.3 (Note that the Maven groupId has changed from <code>org.codehaus.woodstox</code> to <code>com.fasterxml.woodstox</code> and the Maven artifactId has changed from <code>woodstox-core-asl</code> to <code>woodstox-core</code> for this library)</li>
<li>Hibernate ORM (JPA): 5.4.22 -&gt; 5.4.26</li>
<li>Spring (JPA): 5.2.3.RELEASE -&gt; 5.2.9.RELEASE</li>
<li>Datasource-Proxy (JPA): 1.5.1 -&gt; 1.7</li>
<li>Jetty (JPA Starter): 9.4.30.v20200611 -&gt; 9.4.35.v20201120</li>
<li>Hibernate ORM (JPA Server): 5.4.22.FINAL -&gt; 5.4.26.FINAL</li>
<li>Spring (JPA Server): 5.2.9.RELEASE -&gt; 5.3.2</li>
<li>Spring Data (JPA Server): 2.2.0.RELEASE -&gt; 2.4.2</li>
<li>Hibernate Search (JPA Server): 5.11.5.FINAL -&gt; 6.0.0.Final</li>
<li>Lucene(HAPI FHIR JPA Server): 5.5.5 -&gt; 8.7.0</li>
<li>Spring Boot (JPA Starter): 2.2.6.RELEASE -&gt; 2.4.1</li>
</ul>"

View File

@ -16,7 +16,7 @@ As described in the [FHIR specification](http://hl7.org/fhir/observation-operati
# Limitations
Currently only Elasticsearch versions up to 6.5.4 are supported.
Currently only Elasticsearch version 7.10.0 is officially supported.
Search parameters other than those listed above are currently not supported.

View File

@ -1,144 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.3.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<artifactId>hapi-fhir-elasticsearch-6</artifactId>
<name>hapi-fhir-elasticsearch-6</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- Elasticsearch -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.5.4</version>
<exclusions>
<!-- The following all need to be excluded to avoid conflicts with Hibernate-Search -->
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.spullara.mustache.java</groupId>
<artifactId>compiler</artifactId>
</exclusion>
<exclusion>
<groupId>com.tdunning</groupId>
<artifactId>t-digest</artifactId>
</exclusion>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-backward-codecs</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-sandbox</artifactId>
</exclusion>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>jna</artifactId>
</exclusion>
<exclusion>
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
</exclusion>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded6</shadedClassifierName> <!-- Any name that makes sense -->
<relocations>
<relocation>
<pattern>com.carrotsearch.hppc</pattern>
<shadedPattern>com.shadehapi.carrotsearch.hppc</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.logging.log4j</pattern>
<shadedPattern>org.shadehapi.apache.logging.log4j</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.lucene</pattern>
<shadedPattern>org.shadehapi.apache.lucene</shadedPattern>
</relocation>
<relocation>
<pattern>org.elasticsearch</pattern>
<shadedPattern>org.shadehapi.elasticsearch</shadedPattern>
</relocation>
<reloaction>
<pattern>org.joda</pattern>
<shadedPattern>org.shadehapi.joda</shadedPattern>
</reloaction>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -89,8 +89,12 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-mapper-orm</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-backend-elasticsearch</artifactId>
</dependency>
<!-- Spring -->

View File

@ -150,18 +150,6 @@
<artifactId>hapi-fhir-jpaserver-batch</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-elasticsearch-6</artifactId>
<version>${project.version}</version>
<classifier>shaded6</classifier>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.ttddyy</groupId>
@ -478,30 +466,48 @@
<artifactId>javax.el</artifactId>
</dependency>
<!-- Note that we need this dependency to send log4j logging requests to slf4j -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</dependency>
<!-- Hibernate Search -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-mapper-orm</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-backend-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-backend-lucene</artifactId>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-phonetic</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-elasticsearch</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-backward-codecs</artifactId>
</dependency>
<!-- Misc -->
<dependency>
<groupId>com.google.guava</groupId>
@ -579,9 +585,24 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>pl.allegro.tech</groupId>
<artifactId>embedded-elasticsearch</artifactId>
<version>2.10.0</version>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>elasticsearch</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hl7.fhir.testcases</groupId>

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.bulk.job;
import ca.uhn.fhir.context.FhirContext;
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.batch.log.Logs;
import ca.uhn.fhir.jpa.bulk.svc.BulkExportDaoSvc;
import ca.uhn.fhir.jpa.entity.BulkExportCollectionFileEntity;
@ -100,10 +101,10 @@ public class ResourceToFileWriter implements ItemWriter<List<IBaseResource>> {
binary.setContentType(Constants.CT_FHIR_NDJSON);
binary.setContent(myOutputStream.toByteArray());
return myBinaryDao.create(binary).getResource().getIdElement();
DaoMethodOutcome outcome = myBinaryDao.create(binary);
return outcome.getResource().getIdElement();
}
@SuppressWarnings("unchecked")
private IFhirResourceDao<IBaseBinary> getBinaryDao() {
return myDaoRegistry.getResourceDao("Binary");

View File

@ -473,8 +473,8 @@ public abstract class BaseConfig {
}
@Bean
public HibernateDialectProvider hibernateDialectProvider() {
return new HibernateDialectProvider();
public HibernatePropertiesProvider HibernatePropertiesProvider() {
return new HibernatePropertiesProvider();
}
@Bean

View File

@ -21,17 +21,20 @@ package ca.uhn.fhir.jpa.config;
*/
import ca.uhn.fhir.util.ReflectionUtil;
import org.apache.commons.lang3.StringUtils;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import org.hibernate.dialect.Dialect;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
public class HibernateDialectProvider {
public class HibernatePropertiesProvider {
@Autowired
private LocalContainerEntityManagerFactoryBean myEntityManagerFactory;
private Dialect myDialect;
private String myHibernateSearchBackend;
@VisibleForTesting
public void setDialectForUnitTest(Dialect theDialect) {
@ -49,4 +52,13 @@ public class HibernateDialectProvider {
return dialect;
}
public String getHibernateSearchBackend(){
String hibernateSearchBackend = myHibernateSearchBackend;
if (StringUtils.isBlank(hibernateSearchBackend)) {
hibernateSearchBackend = (String) myEntityManagerFactory.getJpaPropertyMap().get(BackendSettings.backendKey(BackendSettings.TYPE));
Validate.notNull(hibernateSearchBackend, BackendSettings.backendKey(BackendSettings.TYPE) + " property is unset!");
myHibernateSearchBackend = hibernateSearchBackend;
}
return myHibernateSearchBackend;
}
}

View File

@ -1031,8 +1031,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
entity.setDeleted(theDeletedTimestampOrNull);
entity.setUpdated(theDeletedTimestampOrNull);
entity.setNarrativeTextParsedIntoWords(null);
entity.setContentTextParsedIntoWords(null);
entity.setNarrativeText(null);
entity.setContentText(null);
entity.setHashSha256(null);
entity.setIndexStatus(INDEX_STATUS_INDEXED);
changed = populateResourceIntoEntity(theRequest, theResource, entity, true);
@ -1058,8 +1058,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
newParams.populateResourceTableSearchParamsPresentFlags(entity);
entity.setIndexStatus(INDEX_STATUS_INDEXED);
populateFullTextFields(myContext, theResource, entity);
}
populateFullTextFields(myContext, theResource, entity);
} else {
changed = populateResourceIntoEntity(theRequest, theResource, entity, false);
@ -1481,11 +1481,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
public static void populateFullTextFields(final FhirContext theContext, final IBaseResource theResource, ResourceTable theEntity) {
if (theEntity.getDeleted() != null) {
theEntity.setNarrativeTextParsedIntoWords(null);
theEntity.setContentTextParsedIntoWords(null);
theEntity.setNarrativeText(null);
theEntity.setContentText(null);
} else {
theEntity.setNarrativeTextParsedIntoWords(parseNarrativeTextIntoWords(theResource));
theEntity.setContentTextParsedIntoWords(parseContentTextIntoWords(theContext, theResource));
theEntity.setNarrativeText(parseNarrativeTextIntoWords(theResource));
theEntity.setContentText(parseContentTextIntoWords(theContext, theResource));
}
}

View File

@ -148,7 +148,7 @@ public abstract class BaseStorageDao {
}
outcome.setId(id);
if (theEntity.isDeleted() == false) {
if (theEntity.getDeleted() == null) {
outcome.setResource(theResource);
}
outcome.setEntity(theEntity);

View File

@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.dao;
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
@ -41,17 +40,21 @@ import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.search.Query;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Scorer;
import org.apache.lucene.search.highlight.TextFragment;
import org.apache.lucene.search.highlight.TokenGroup;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.backend.lucene.index.LuceneIndexManager;
import org.hibernate.search.engine.search.predicate.dsl.BooleanPredicateClausesStep;
import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory;
import org.hibernate.search.engine.search.query.SearchQuery;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.mapping.SearchMapping;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
@ -60,12 +63,14 @@ import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -74,15 +79,13 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager myEntityManager;
@Autowired
private PlatformTransactionManager myTxManager;
@Autowired
protected IForcedIdDao myForcedIdDao;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private IdHelperService myIdHelperService;
@ -95,136 +98,81 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
super();
}
private void addTextSearch(QueryBuilder theQueryBuilder, BooleanJunction<?> theBoolean, List<List<IQueryParameterType>> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameNGram) {
private void addTextSearch(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, List<List<IQueryParameterType>> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameTextNGram){
if (theTerms == null) {
return;
}
for (List<? extends IQueryParameterType> nextAnd : theTerms) {
Set<String> terms = new HashSet<>();
for (IQueryParameterType nextOr : nextAnd) {
StringParam nextOrString = (StringParam) nextOr;
String nextValueTrimmed = StringUtils.defaultString(nextOrString.getValue()).trim();
if (isNotBlank(nextValueTrimmed)) {
terms.add(nextValueTrimmed);
}
}
if (terms.isEmpty() == false) {
Set<String> terms = extractOrStringParams(nextAnd);
if (terms.size() == 1) {
//@formatter:off
Query textQuery = theQueryBuilder
.phrase()
.withSlop(2)
.onField(theFieldName).boostedTo(4.0f)
// .andField(theFieldNameEdgeNGram).boostedTo(2.0f)
// .andField(theFieldNameNGram).boostedTo(1.0f)
.sentence(terms.iterator().next().toLowerCase()).createQuery();
//@formatter:on
theBoolean.must(textQuery);
} else {
b.must(f.phrase()
.field(theFieldName)
.boost(4.0f)
.matching(terms.iterator().next().toLowerCase())
.slop(2));
} else if (terms.size() > 1){
String joinedTerms = StringUtils.join(terms, ' ');
theBoolean.must(theQueryBuilder.keyword().onField(theFieldName).matching(joinedTerms).createQuery());
b.must(f.match().field(theFieldName).matching(joinedTerms));
} else {
ourLog.debug("No Terms found in query parameter {}", nextAnd);
}
}
}
@NotNull
private Set<String> extractOrStringParams(List<? extends IQueryParameterType> nextAnd) {
Set<String> terms = new HashSet<>();
for (IQueryParameterType nextOr : nextAnd) {
StringParam nextOrString = (StringParam) nextOr;
String nextValueTrimmed = StringUtils.defaultString(nextOrString.getValue()).trim();
if (isNotBlank(nextValueTrimmed)) {
terms.add(nextValueTrimmed);
}
}
return terms;
}
private List<ResourcePersistentId> doSearch(String theResourceName, SearchParameterMap theParams, ResourcePersistentId theReferencingPid) {
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
List<ResourcePersistentId> pids = null;
/*
* Handle textual params
*/
/*
for (String nextParamName : theParams.keySet()) {
for (List<? extends IQueryParameterType> nextAndList : theParams.get(nextParamName)) {
for (Iterator<? extends IQueryParameterType> orIterator = nextAndList.iterator(); orIterator.hasNext();) {
IQueryParameterType nextParam = orIterator.next();
if (nextParam instanceof TokenParam) {
TokenParam nextTokenParam = (TokenParam) nextParam;
if (nextTokenParam.isText()) {
orIterator.remove();
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceIndexedSearchParamString.class).get();
BooleanJunction<?> bool = qb.bool();
bool.must(qb.keyword().onField("myParamName").matching(nextParamName).createQuery());
if (isNotBlank(theResourceName)) {
bool.must(qb.keyword().onField("myResourceType").matching(theResourceName).createQuery());
}
//
//@formatter:off
String value = nextTokenParam.getValue().toLowerCase();
bool.must(qb.keyword().onField("myValueTextEdgeNGram").matching(value).createQuery());
//@formatter:on
FullTextQuery ftq = em.createFullTextQuery(bool.createQuery(), ResourceIndexedSearchParamString.class);
List<?> resultList = ftq.getResultList();
pids = new ArrayList<Long>();
for (Object next : resultList) {
ResourceIndexedSearchParamString nextAsArray = (ResourceIndexedSearchParamString) next;
pids.add(nextAsArray.getResourcePid());
}
}
}
}
}
}
if (pids != null && pids.isEmpty()) {
return pids;
}
*/
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
BooleanJunction<?> bool = qb.bool();
/*
* Handle _content parameter (resource body content)
*/
SearchSession session = Search.session(myEntityManager);
List<List<IQueryParameterType>> contentAndTerms = theParams.remove(Constants.PARAM_CONTENT);
addTextSearch(qb, bool, contentAndTerms, "myContentText", "myContentTextEdgeNGram", "myContentTextNGram");
/*
* Handle _text parameter (resource narrative content)
*/
List<List<IQueryParameterType>> textAndTerms = theParams.remove(Constants.PARAM_TEXT);
addTextSearch(qb, bool, textAndTerms, "myNarrativeText", "myNarrativeTextEdgeNGram", "myNarrativeTextNGram");
if (theReferencingPid != null) {
bool.must(qb.keyword().onField("myResourceLinksField").matching(theReferencingPid.toString()).createQuery());
}
List<Long> longPids = session.search(ResourceTable.class)
//Selects are replacements for projection and convert more cleanly than the old implementation.
.select(
f -> f.field("myId", Long.class)
)
.where(
f -> f.bool(b -> {
/*
* Handle _content parameter (resource body content)
*/
addTextSearch(f, b, contentAndTerms, "myContentText", "mycontentTextEdgeNGram", "myContentTextNGram");
/*
* Handle _text parameter (resource narrative content)
*/
addTextSearch(f, b, textAndTerms, "myNarrativeText", "myNarrativeTextEdgeNGram", "myNarrativeTextNGram");
if (bool.isEmpty()) {
return pids;
}
if (theReferencingPid != null) {
b.must(f.match().field("myResourceLinksField").matching(theReferencingPid.toString()));
}
if (isNotBlank(theResourceName)) {
bool.must(qb.keyword().onField("myResourceType").matching(theResourceName).createQuery());
}
//DROP EARLY HERE IF BOOL IS EMPTY?
Query luceneQuery = bool.createQuery();
if (isNotBlank(theResourceName)) {
b.must(f.match().field("myResourceType").matching(theResourceName));
}
})
).fetchAllHits();
// wrap Lucene query in a javax.persistence.SqlQuery
FullTextQuery jpaQuery = em.createFullTextQuery(luceneQuery, ResourceTable.class);
jpaQuery.setProjection("myId");
return convertLongsToResourcePersistentIds(longPids);
}
// execute search
List<?> result = jpaQuery.getResultList();
ArrayList<ResourcePersistentId> retVal = new ArrayList<>();
for (Object object : result) {
Object[] nextArray = (Object[]) object;
Long next = (Long) nextArray[0];
if (next != null) {
retVal.add(new ResourcePersistentId(next));
}
}
return retVal;
private List<ResourcePersistentId> convertLongsToResourcePersistentIds(List<Long> theLongPids) {
return theLongPids.stream()
.map(pid -> new ResourcePersistentId(pid))
.collect(Collectors.toList());
}
@Override
@ -259,8 +207,8 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
if (retVal == null) {
retVal = new TransactionTemplate(myTxManager).execute(t -> {
try {
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
SearchSession searchSession = Search.session(myEntityManager);
searchSession.search(ResourceTable.class);
return Boolean.FALSE;
} catch (Exception e) {
ourLog.trace("FullText test failed", e);
@ -287,172 +235,4 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
@Autowired
private PartitionSettings myPartitionSettings;
@Transactional()
@Override
public List<Suggestion> suggestKeywords(String theContext, String theSearchParam, String theText, RequestDetails theRequest) {
Validate.notBlank(theContext, "theContext must be provided");
Validate.notBlank(theSearchParam, "theSearchParam must be provided");
Validate.notBlank(theText, "theSearchParam must be provided");
long start = System.currentTimeMillis();
String[] contextParts = StringUtils.split(theContext, '/');
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
throw new InvalidRequestException("Invalid context: " + theContext);
}
// Partitioning is not supported for this operation
Validate.isTrue(myPartitionSettings.isPartitioningEnabled() == false, "Suggest keywords not supported for partitioned system");
RequestPartitionId requestPartitionId = null;
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, contextParts[0], contextParts[1]);
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
Query textQuery = qb
.phrase()
.withSlop(2)
.onField("myContentText").boostedTo(4.0f)
.andField("myContentTextEdgeNGram").boostedTo(2.0f)
.andField("myContentTextNGram").boostedTo(1.0f)
.andField("myContentTextPhonetic").boostedTo(0.5f)
.sentence(theText.toLowerCase()).createQuery();
Query query = qb.bool()
.must(qb.keyword().onField("myResourceLinksField").matching(pid.toString()).createQuery())
.must(textQuery)
.createQuery();
FullTextQuery ftq = em.createFullTextQuery(query, ResourceTable.class);
ftq.setProjection("myContentText");
ftq.setMaxResults(20);
List<?> resultList = ftq.getResultList();
List<Suggestion> suggestions = Lists.newArrayList();
for (Object next : resultList) {
Object[] nextAsArray = (Object[]) next;
String nextValue = (String) nextAsArray[0];
try {
MySuggestionFormatter formatter = new MySuggestionFormatter(theText, suggestions);
Scorer scorer = new QueryScorer(textQuery);
Highlighter highlighter = new Highlighter(formatter, scorer);
Analyzer analyzer = em.getSearchFactory().getAnalyzer(ResourceTable.class);
formatter.setAnalyzer("myContentTextPhonetic");
highlighter.getBestFragments(analyzer.tokenStream("myContentTextPhonetic", nextValue), nextValue, 10);
formatter.setAnalyzer("myContentTextNGram");
highlighter.getBestFragments(analyzer.tokenStream("myContentTextNGram", nextValue), nextValue, 10);
formatter.setFindPhrasesWith();
formatter.setAnalyzer("myContentTextEdgeNGram");
highlighter.getBestFragments(analyzer.tokenStream("myContentTextEdgeNGram", nextValue), nextValue, 10);
} catch (Exception e) {
throw new InternalErrorException(e);
}
}
Collections.sort(suggestions);
Set<String> terms = Sets.newHashSet();
for (Iterator<Suggestion> iter = suggestions.iterator(); iter.hasNext(); ) {
String nextTerm = iter.next().getTerm().toLowerCase();
if (!terms.add(nextTerm)) {
iter.remove();
}
}
long delay = System.currentTimeMillis() - start;
ourLog.info("Provided {} suggestions for term {} in {} ms", terms.size(), theText, delay);
return suggestions;
}
public class MySuggestionFormatter implements Formatter {
private List<Suggestion> mySuggestions;
private String myAnalyzer;
private ArrayList<String> myPartialMatchPhrases;
private ArrayList<Float> myPartialMatchScores;
private String myOriginalSearch;
MySuggestionFormatter(String theOriginalSearch, List<Suggestion> theSuggestions) {
myOriginalSearch = theOriginalSearch;
mySuggestions = theSuggestions;
}
@Override
public String highlightTerm(String theOriginalText, TokenGroup theTokenGroup) {
ourLog.debug("{} Found {} with score {}", myAnalyzer, theOriginalText, theTokenGroup.getTotalScore());
if (theTokenGroup.getTotalScore() > 0) {
float score = theTokenGroup.getTotalScore();
if (theOriginalText.equalsIgnoreCase(myOriginalSearch)) {
score = score + 1.0f;
}
mySuggestions.add(new Suggestion(theOriginalText, score));
} else if (myPartialMatchPhrases != null) {
if (theOriginalText.length() < 100) {
for (int i = 0; i < myPartialMatchPhrases.size(); i++) {
if (theOriginalText.contains(myPartialMatchPhrases.get(i))) {
mySuggestions.add(new Suggestion(theOriginalText, myPartialMatchScores.get(i) - 0.5f));
}
}
}
}
return null;
}
void setAnalyzer(String theString) {
myAnalyzer = theString;
}
void setFindPhrasesWith() {
myPartialMatchPhrases = new ArrayList<>();
myPartialMatchScores = new ArrayList<>();
for (Suggestion next : mySuggestions) {
myPartialMatchPhrases.add(' ' + next.myTerm);
myPartialMatchScores.add(next.myScore);
}
myPartialMatchPhrases.add(myOriginalSearch);
myPartialMatchScores.add(1.0f);
}
}
public static class Suggestion implements Comparable<Suggestion> {
private String myTerm;
private float myScore;
Suggestion(String theTerm, float theScore) {
myTerm = theTerm;
myScore = theScore;
}
@Override
public int compareTo(Suggestion theO) {
return Float.compare(theO.myScore, myScore);
}
public float getScore() {
return myScore;
}
public String getTerm() {
return myTerm;
}
@Override
public String toString() {
return "Suggestion[myTerm=" + myTerm + ", myScore=" + myScore + "]";
}
}
}

View File

@ -22,15 +22,13 @@ package ca.uhn.fhir.jpa.dao;
import java.util.List;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.RequestDetails;
public interface IFulltextSearchSvc {
List<Suggestion> suggestKeywords(String theContext, String theSearchParam, String theText, RequestDetails theRequest);
List<ResourcePersistentId> search(String theResourceName, SearchParameterMap theParams);
List<ResourcePersistentId> everything(String theResourceName, SearchParameterMap theParams, RequestDetails theRequest);

View File

@ -1,29 +0,0 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%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 org.springframework.data.jpa.repository.JpaRepository;
public interface IHapiJpaRepository<T> extends JpaRepository<T, Long> {
void deleteByPid(Long theId);
}

View File

@ -35,10 +35,6 @@ public interface ITermCodeSystemVersionDao extends JpaRepository<TermCodeSystemV
@Query("DELETE FROM TermCodeSystemVersion csv WHERE csv.myCodeSystem = :cs")
void deleteForCodeSystem(@Param("cs") TermCodeSystem theCodeSystem);
@Modifying
@Query("DELETE FROM TermCodeSystemVersion csv WHERE csv.myId = :pid")
void delete(@Param("pid") Long codesystemversion_pid);
@Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myCodeSystemPid = :codesystem_pid")
List<TermCodeSystemVersion> findByCodeSystemPid(@Param("codesystem_pid") Long theCodeSystemPid);

View File

@ -1,12 +1,11 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.dao.IHapiJpaRepository;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@ -33,7 +32,7 @@ import java.util.Optional;
* #L%
*/
public interface ITermConceptDao extends IHapiJpaRepository<TermConcept> {
public interface ITermConceptDao extends JpaRepository<TermConcept, Long> {
@Query("SELECT COUNT(t) FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid")
Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid);
@ -50,9 +49,4 @@ public interface ITermConceptDao extends IHapiJpaRepository<TermConcept> {
@Query("SELECT t FROM TermConcept t WHERE t.myIndexStatus = null")
Page<TermConcept> findResourcesRequiringReindexing(Pageable thePageRequest);
@Override
@Modifying
@Query("DELETE FROM TermConcept t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId);
}

View File

@ -1,10 +1,9 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.dao.IHapiJpaRepository;
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@ -28,7 +27,7 @@ import org.springframework.data.repository.query.Param;
* #L%
*/
public interface ITermConceptDesignationDao extends IHapiJpaRepository<TermConceptDesignation> {
public interface ITermConceptDesignationDao extends JpaRepository<TermConceptDesignation, Long> {
@Query("SELECT t.myId FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("csv_pid") Long thePid);
@ -36,9 +35,4 @@ public interface ITermConceptDesignationDao extends IHapiJpaRepository<TermConce
@Query("SELECT COUNT(t) FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Integer countByCodeSystemVersion(@Param("csv_pid") Long thePid);
@Override
@Modifying
@Query("DELETE FROM TermConceptDesignation t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId);
}

View File

@ -1,10 +1,9 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.dao.IHapiJpaRepository;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@ -30,7 +29,7 @@ import java.util.Collection;
* #L%
*/
public interface ITermConceptParentChildLinkDao extends IHapiJpaRepository<TermConceptParentChildLink> {
public interface ITermConceptParentChildLinkDao extends JpaRepository<TermConceptParentChildLink, Long> {
@Query("SELECT COUNT(t) FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid")
Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid);
@ -41,13 +40,4 @@ public interface ITermConceptParentChildLinkDao extends IHapiJpaRepository<TermC
@Query("SELECT t.myPid FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Modifying
@Query("DELETE FROM TermConceptParentChildLink t WHERE t.myChildPid = :pid OR t.myParentPid = :pid")
void deleteByConceptPid(@Param("pid") Long theId);
@Override
@Modifying
@Query("DELETE FROM TermConceptParentChildLink t WHERE t.myPid = :pid")
void deleteByPid(@Param("pid") Long theId);
}

View File

@ -1,10 +1,9 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.dao.IHapiJpaRepository;
import ca.uhn.fhir.jpa.entity.TermConceptProperty;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@ -28,7 +27,7 @@ import org.springframework.data.repository.query.Param;
* #L%
*/
public interface ITermConceptPropertyDao extends IHapiJpaRepository<TermConceptProperty> {
public interface ITermConceptPropertyDao extends JpaRepository<TermConceptProperty, Long> {
@Query("SELECT t.myId FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@ -36,9 +35,4 @@ public interface ITermConceptPropertyDao extends IHapiJpaRepository<TermConceptP
@Query("SELECT COUNT(t) FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid);
@Override
@Modifying
@Query("DELETE FROM TermConceptProperty t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId);
}

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchInclude;
import ca.uhn.fhir.jpa.entity.SearchResult;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
@ -162,7 +163,7 @@ public class ExpungeEverythingService {
counter.addAndGet(expungeEverythingByType(ResourceTable.class));
counter.addAndGet(expungeEverythingByType(PartitionEntity.class));
myTxTemplate.execute(t -> {
counter.addAndGet(doExpungeEverythingQuery("DELETE from " + org.hibernate.search.jpa.Search.class.getSimpleName() + " d"));
counter.addAndGet(doExpungeEverythingQuery("DELETE from " + Search.class.getSimpleName() + " d"));
return null;
});

View File

@ -24,15 +24,14 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.jpa.util.SearchBox;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.dstu2.resource.Location;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.SpecialParam;
import ca.uhn.fhir.rest.param.TokenParam;
import com.google.common.annotations.VisibleForTesting;
import org.hibernate.search.engine.spatial.GeoBoundingBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
@ -116,7 +115,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
double latitudeDegrees = Double.parseDouble(latitudeValue);
double longitudeDegrees = Double.parseDouble(longitudeValue);
SearchBox box = CoordCalculator.getBox(latitudeDegrees, longitudeDegrees, distanceKm);
GeoBoundingBox box = CoordCalculator.getBox(latitudeDegrees, longitudeDegrees, distanceKm);
latitudePredicate = latitudePredicateFromBox(theBuilder, theFrom, box);
longitudePredicate = longitudePredicateFromBox(theBuilder, theFrom, box);
}
@ -124,24 +123,24 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theSearchParam.getName(), theFrom, singleCode, theRequestPartitionId);
}
private Predicate latitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, SearchBox theBox) {
private Predicate latitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, GeoBoundingBox theBox) {
return theBuilder.and(
theBuilder.greaterThanOrEqualTo(theFrom.get("myLatitude"), theBox.getSouthWest().getLatitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLatitude"), theBox.getNorthEast().getLatitude())
theBuilder.greaterThanOrEqualTo(theFrom.get("myLatitude"), theBox.bottomRight().latitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLatitude"), theBox.topLeft().latitude())
);
}
@VisibleForTesting
Predicate longitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, SearchBox theBox) {
if (theBox.crossesAntiMeridian()) {
Predicate longitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, GeoBoundingBox theBox) {
if (theBox.bottomRight().longitude() < theBox.topLeft().longitude()) {
return theBuilder.or(
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), theBox.getNorthEast().getLongitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), theBox.getSouthWest().getLongitude())
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), theBox.bottomRight().longitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), theBox.topLeft().longitude())
);
}
return theBuilder.and(
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), theBox.getSouthWest().getLongitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), theBox.getNorthEast().getLongitude())
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), theBox.topLeft().longitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), theBox.bottomRight().longitude())
);
}

View File

@ -285,7 +285,12 @@ public class MdmLink {
return myEidMatch;
}
public boolean isEidMatch() {
/**
* Note that this method can not be called <code>getEidMatch</code> or
* <code>isEidMatch</code> because Hibernate Search complains about having
* 2 accessors for this property
*/
public boolean isEidMatchPresent() {
return myEidMatch != null && myEidMatch;
}

View File

@ -40,6 +40,7 @@ import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import java.io.Serializable;
import java.util.ArrayList;
@ -57,6 +58,7 @@ public class TermCodeSystemVersion implements Serializable {
public static final String IDX_CODESYSTEM_AND_VER = "IDX_CODESYSTEM_AND_VER";
public static final int MAX_VERSION_LENGTH = 200;
private static final long serialVersionUID = 1L;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem")
private Collection<TermConcept> myConcepts;
@ -73,7 +75,7 @@ public class TermCodeSystemVersion implements Serializable {
@Column(name = "RES_ID", nullable = false, insertable = false, updatable = false)
private Long myResourcePid;
@Column(name = "CS_VERSION_ID", nullable = true, updatable = false, length = MAX_VERSION_LENGTH)
@Column(name = "CS_VERSION_ID", nullable = true, updatable = true, length = MAX_VERSION_LENGTH)
private String myCodeSystemVersionId;
/**
@ -91,7 +93,7 @@ public class TermCodeSystemVersion implements Serializable {
@OneToOne(mappedBy = "myCurrentVersion", optional = true, fetch = FetchType.LAZY)
private TermCodeSystem myCodeSystemHavingThisVersionAsCurrentVersionIfAny;
@Column(name = "CS_DISPLAY", nullable = true, updatable = false, length = MAX_VERSION_LENGTH)
@Column(name = "CS_DISPLAY", nullable = true, updatable = true, length = MAX_VERSION_LENGTH)
private String myCodeSystemDisplayName;
/**
@ -196,6 +198,7 @@ public class TermCodeSystemVersion implements Serializable {
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("pid", myId);
b.append("displayName", myCodeSystemDisplayName);
b.append("codeSystemResourcePid", myResourcePid);
b.append("codeSystemPid", myCodeSystemPid);
b.append("codeSystemVersionId", myCodeSystemVersionId);

View File

@ -22,14 +22,21 @@ package ca.uhn.fhir.jpa.entity;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor;
import ca.uhn.fhir.jpa.search.DeferConceptIndexingRoutingBinder;
import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.*;
import org.hibernate.search.engine.backend.types.Projectable;
import org.hibernate.search.engine.backend.types.Searchable;
import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.PropertyBinderRef;
import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.RoutingBinderRef;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyBinding;
import org.hl7.fhir.r4.model.Coding;
import javax.annotation.Nonnull;
@ -43,7 +50,7 @@ import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.length;
@Entity
@Indexed(interceptor = DeferConceptIndexingInterceptor.class)
@Indexed(routingBinder=@RoutingBinderRef(type = DeferConceptIndexingRoutingBinder.class))
@Table(name = "TRM_CONCEPT", uniqueConstraints = {
@UniqueConstraint(name = "IDX_CONCEPT_CS_CODE", columnNames = {"CODESYSTEM_PID", "CODEVAL"})
}, indexes = {
@ -55,49 +62,59 @@ public class TermConcept implements Serializable {
public static final int MAX_DESC_LENGTH = 400;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class);
private static final long serialVersionUID = 1L;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {})
private List<TermConceptParentChildLink> myChildren;
@Column(name = "CODEVAL", nullable = false, length = MAX_CODE_LENGTH)
@Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),})
@FullTextField(name = "myCode", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "exactAnalyzer")
private String myCode;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "CONCEPT_UPDATED", nullable = true)
private Date myUpdated;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPT_PID_CS_PID"))
private TermCodeSystemVersion myCodeSystem;
@Column(name = "CODESYSTEM_PID", insertable = false, updatable = false)
@Fields({@Field(name = "myCodeSystemVersionPid")})
@GenericField(name = "myCodeSystemVersionPid")
private long myCodeSystemVersionPid;
@Column(name = "DISPLAY", nullable = true, length = MAX_DESC_LENGTH)
@Fields({
@Field(name = "myDisplay", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
@Field(name = "myDisplayEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")),
@Field(name = "myDisplayWordEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteWordEdgeAnalyzer")),
@Field(name = "myDisplayNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
@Field(name = "myDisplayPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
})
@FullTextField(name = "myDisplay", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer")
@FullTextField(name = "myDisplayEdgeNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteEdgeAnalyzer")
@FullTextField(name = "myDisplayWordEdgeNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteWordEdgeAnalyzer")
@FullTextField(name = "myDisplayNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteNGramAnalyzer")
@FullTextField(name = "myDisplayPhonetic", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompletePhoneticAnalyzer")
private String myDisplay;
@OneToMany(mappedBy = "myConcept", orphanRemoval = false, fetch = FetchType.LAZY)
@Field(name = "PROPmyProperties", analyzer = @Analyzer(definition = "termConceptPropertyAnalyzer"))
@FieldBridge(impl = TermConceptPropertyFieldBridge.class)
@PropertyBinding(binder = @PropertyBinderRef(type = TermConceptPropertyBinder.class))
private Collection<TermConceptProperty> myProperties;
@OneToMany(mappedBy = "myConcept", orphanRemoval = false, fetch = FetchType.LAZY)
private Collection<TermConceptDesignation> myDesignations;
@Id()
@Id
@SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PID")
@Column(name = "PID")
@GenericField
private Long myId;
@Column(name = "INDEX_STATUS", nullable = true)
private Long myIndexStatus;
@Field(name = "myParentPids", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "conceptParentPidsAnalyzer"))
@Lob
@Column(name = "PARENT_PIDS", nullable = true)
@FullTextField(name = "myParentPids", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "conceptParentPidsAnalyzer")
private String myParentPids;
@OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "myChild")
private List<TermConceptParentChildLink> myParents;
@Column(name = "CODE_SEQUENCE", nullable = true)
private Integer mySequence;
@ -382,6 +399,8 @@ public class TermConcept implements Serializable {
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("pid", myId);
b.append("csvPid", myCodeSystemVersionPid);
b.append("code", myCode);
b.append("display", myDisplay);
if (mySequence != null) {

View File

@ -131,4 +131,7 @@ public class TermConceptDesignation implements Serializable {
}
public Long getPid() {
return myId;
}
}

View File

@ -20,8 +20,7 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import javax.persistence.*;
import java.io.Serializable;
@ -44,7 +43,7 @@ public class TermConceptParentChildLink implements Serializable {
private TermCodeSystemVersion myCodeSystem;
@Column(name = "CODESYSTEM_PID", insertable = false, updatable = false, nullable = false)
@Fields({@Field(name = "myCodeSystemVersionPid")})
@FullTextField(name = "myCodeSystemVersionPid")
private long myCodeSystemVersionPid;
@ManyToOne(fetch = FetchType.LAZY, cascade = {})

View File

@ -251,4 +251,8 @@ public class TermConceptProperty implements Serializable {
.append(myDisplay)
.toHashCode();
}
public Long getPid() {
return myId;
}
}

View File

@ -0,0 +1,77 @@
package ca.uhn.fhir.jpa.entity;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hibernate.search.engine.backend.document.DocumentElement;
import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement;
import org.hibernate.search.mapper.pojo.bridge.PropertyBridge;
import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext;
import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder;
import org.hibernate.search.mapper.pojo.bridge.runtime.PropertyBridgeWriteContext;
import java.util.Collection;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Allows hibernate search to index individual concepts' properties
*/
public class TermConceptPropertyBinder implements PropertyBinder {
public static final String CONCEPT_FIELD_PROPERTY_PREFIX = "PROP";
@Override
public void bind(PropertyBindingContext thePropertyBindingContext) {
thePropertyBindingContext.dependencies().use("myKey").use("myValue");
IndexSchemaElement indexSchemaElement = thePropertyBindingContext.indexSchemaElement();
//In order to support dynamic fields, we have to use field templates. We _must_ define the template at bootstrap time and cannot
//create them adhoc. https://docs.jboss.org/hibernate/search/6.0/reference/en-US/html_single/#mapper-orm-bridge-index-field-dsl-dynamic
//I _think_ im doing the right thing here by indicating that everything matching this template uses this analyzer.
indexSchemaElement.fieldTemplate("propTemplate", f -> f.asString().analyzer("termConceptPropertyAnalyzer"))
.matchingPathGlob(CONCEPT_FIELD_PROPERTY_PREFIX + "*")
.multiValued();
thePropertyBindingContext.bridge(new TermConceptPropertyBridge());
}
private class TermConceptPropertyBridge implements PropertyBridge {
@Override
public void write(DocumentElement theDocument, Object theObject, PropertyBridgeWriteContext thePropertyBridgeWriteContext) {
Collection<TermConceptProperty> properties = (Collection<TermConceptProperty>) theObject;
if (properties != null) {
for (TermConceptProperty next : properties) {
theDocument.addValue(CONCEPT_FIELD_PROPERTY_PREFIX + next.getKey(), next.getValue());
System.out.println("Adding Prop: " + CONCEPT_FIELD_PROPERTY_PREFIX + next.getKey() + " -- " + next.getValue());
if (next.getType() == TermConceptPropertyTypeEnum.CODING && isNotBlank(next.getDisplay())) {
theDocument.addValue(CONCEPT_FIELD_PROPERTY_PREFIX + next.getKey(), next.getDisplay());
System.out.println("Adding multivalue Prop: " + CONCEPT_FIELD_PROPERTY_PREFIX + next.getKey() + " -- " + next.getDisplay());
}
}
}
}
}
}

View File

@ -1,70 +0,0 @@
package ca.uhn.fhir.jpa.entity;
/*-
* #%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 org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.LuceneOptions;
import org.hibernate.search.bridge.StringBridge;
import java.util.Collection;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Allows hibernate search to index individual concepts' properties
*/
public class TermConceptPropertyFieldBridge implements FieldBridge, StringBridge {
public static final String CONCEPT_FIELD_PROPERTY_PREFIX = "PROP";
/**
* Constructor
*/
public TermConceptPropertyFieldBridge() {
super();
}
@Override
public String objectToString(Object theObject) {
return theObject.toString();
}
@Override
public void set(String theName, Object theValue, Document theDocument, LuceneOptions theLuceneOptions) {
@SuppressWarnings("unchecked")
Collection<TermConceptProperty> properties = (Collection<TermConceptProperty>) theValue;
if (properties != null) {
for (TermConceptProperty next : properties) {
theDocument.add(new StringField(CONCEPT_FIELD_PROPERTY_PREFIX + next.getKey(), next.getValue(), Field.Store.YES));
if (next.getType() == TermConceptPropertyTypeEnum.CODING) {
if (isNotBlank(next.getDisplay())) {
theDocument.add(new StringField(CONCEPT_FIELD_PROPERTY_PREFIX + next.getKey(), next.getDisplay(), Field.Store.YES));
}
}
}
}
}
}

View File

@ -1,17 +1,14 @@
package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Parameters.Parameter;
import ca.uhn.fhir.model.primitive.BooleanDt;
import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.IdParam;
@ -20,7 +17,6 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IIdType;
@ -216,36 +212,6 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
return parameters;
}
@Operation(name = JpaConstants.OPERATION_SUGGEST_KEYWORDS, idempotent = true)
public Parameters suggestKeywords(
@OperationParam(name = "context", min = 1, max = 1) String theContext,
@OperationParam(name = "searchParam", min = 1, max = 1) String theSearchParam,
@OperationParam(name = "text", min = 1, max = 1) String theText,
RequestDetails theRequest) {
JpaSystemProviderDstu3.validateFulltextSearchEnabled(mySearchDao);
if (isBlank(theContext)) {
throw new InvalidRequestException("Parameter 'context' must be provided");
}
if (isBlank(theSearchParam)) {
throw new InvalidRequestException("Parameter 'searchParam' must be provided");
}
if (isBlank(theText)) {
throw new InvalidRequestException("Parameter 'text' must be provided");
}
List<Suggestion> keywords = mySearchDao.suggestKeywords(theContext, theSearchParam, theText, theRequest);
Parameters retVal = new Parameters();
for (Suggestion next : keywords) {
retVal.addParameter()
.addPart(new Parameter().setName("keyword").setValue(new StringDt(next.getTerm())))
.addPart(new Parameter().setName("score").setValue(new DecimalDt(next.getScore())));
}
return retVal;
}
@Transaction
public Bundle transaction(RequestDetails theRequestDetails, @TransactionParam Bundle theResources) {
startRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
@ -224,36 +223,6 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
return parameters;
}
@Operation(name = JpaConstants.OPERATION_SUGGEST_KEYWORDS, idempotent = true)
public Parameters suggestKeywords(
@OperationParam(name = "context", min = 1, max = 1) String theContext,
@OperationParam(name = "searchParam", min = 1, max = 1) String theSearchParam,
@OperationParam(name = "text", min = 1, max = 1) String theText,
RequestDetails theRequest) {
if (isBlank(theContext)) {
throw new InvalidRequestException("Parameter 'context' must be provided");
}
if (isBlank(theSearchParam)) {
throw new InvalidRequestException("Parameter 'searchParam' must be provided");
}
if (isBlank(theText)) {
throw new InvalidRequestException("Parameter 'text' must be provided");
}
List<Suggestion> keywords = mySearchDao.suggestKeywords(theContext, theSearchParam, theText, theRequest);
Parameters retVal = new Parameters();
for (Suggestion next : keywords) {
retVal.addParameter()
.addPart(new ParametersParameterComponent().setName("keyword").setValue(new StringType(next.getTerm())))
.addPart(new ParametersParameterComponent().setName("score").setValue(new DecimalType(next.getScore())));
}
return retVal;
}
@Transaction
public Bundle transaction(RequestDetails theRequestDetails, @TransactionParam Bundle theResources) {
startRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
@ -211,38 +210,6 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus<Bundle,
return parameters;
}
@Operation(name = JpaConstants.OPERATION_SUGGEST_KEYWORDS, idempotent = true)
public Parameters suggestKeywords(
@OperationParam(name = "context", min = 1, max = 1) String theContext,
@OperationParam(name = "searchParam", min = 1, max = 1) String theSearchParam,
@OperationParam(name = "text", min = 1, max = 1) String theText,
RequestDetails theRequest) {
ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3.validateFulltextSearchEnabled(mySearchDao);
if (isBlank(theContext)) {
throw new InvalidRequestException("Parameter 'context' must be provided");
}
if (isBlank(theSearchParam)) {
throw new InvalidRequestException("Parameter 'searchParam' must be provided");
}
if (isBlank(theText)) {
throw new InvalidRequestException("Parameter 'text' must be provided");
}
List<Suggestion> keywords = mySearchDao.suggestKeywords(theContext, theSearchParam, theText, theRequest);
Parameters retVal = new Parameters();
for (Suggestion next : keywords) {
//@formatter:off
retVal.addParameter()
.addPart(new ParametersParameterComponent().setName("keyword").setValue(new StringType(next.getTerm())))
.addPart(new ParametersParameterComponent().setName("score").setValue(new DecimalType(next.getScore())));
//@formatter:on
}
return retVal;
}
/**
* /$process-message
*/

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.provider.r5;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
@ -213,38 +212,6 @@ public class JpaSystemProviderR5 extends BaseJpaSystemProviderDstu2Plus<Bundle,
return parameters;
}
@Operation(name = JpaConstants.OPERATION_SUGGEST_KEYWORDS, idempotent = true)
public Parameters suggestKeywords(
@OperationParam(name = "context", min = 1, max = 1) String theContext,
@OperationParam(name = "searchParam", min = 1, max = 1) String theSearchParam,
@OperationParam(name = "text", min = 1, max = 1) String theText,
RequestDetails theRequest) {
ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3.validateFulltextSearchEnabled(mySearchDao);
if (isBlank(theContext)) {
throw new InvalidRequestException("Parameter 'context' must be provided");
}
if (isBlank(theSearchParam)) {
throw new InvalidRequestException("Parameter 'searchParam' must be provided");
}
if (isBlank(theText)) {
throw new InvalidRequestException("Parameter 'text' must be provided");
}
List<Suggestion> keywords = mySearchDao.suggestKeywords(theContext, theSearchParam, theText, theRequest);
Parameters retVal = new Parameters();
for (Suggestion next : keywords) {
//@formatter:off
retVal.addParameter()
.addPart(new ParametersParameterComponent().setName("keyword").setValue(new StringType(next.getTerm())))
.addPart(new ParametersParameterComponent().setName("score").setValue(new DecimalType(next.getScore())));
//@formatter:on
}
return retVal;
}
/**
* /$process-message
*/

View File

@ -1,55 +0,0 @@
package ca.uhn.fhir.jpa.search;
/*
* #%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 org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor;
import org.hibernate.search.indexes.interceptor.IndexingOverride;
import ca.uhn.fhir.jpa.entity.TermConcept;
public class DeferConceptIndexingInterceptor implements EntityIndexingInterceptor<TermConcept> {
@Override
public IndexingOverride onAdd(TermConcept theEntity) {
if (theEntity.getIndexStatus() == null) {
return IndexingOverride.SKIP;
}
return IndexingOverride.APPLY_DEFAULT;
}
@Override
public IndexingOverride onCollectionUpdate(TermConcept theEntity) {
return IndexingOverride.APPLY_DEFAULT;
}
@Override
public IndexingOverride onDelete(TermConcept theEntity) {
return IndexingOverride.APPLY_DEFAULT;
}
@Override
public IndexingOverride onUpdate(TermConcept theEntity) {
return onAdd(theEntity);
}
}

View File

@ -0,0 +1,53 @@
package ca.uhn.fhir.jpa.search;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.entity.TermConcept;
import org.hibernate.search.mapper.pojo.bridge.RoutingBridge;
import org.hibernate.search.mapper.pojo.bridge.binding.RoutingBindingContext;
import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.RoutingBinder;
import org.hibernate.search.mapper.pojo.bridge.runtime.RoutingBridgeRouteContext;
import org.hibernate.search.mapper.pojo.route.DocumentRoutes;
public class DeferConceptIndexingRoutingBinder implements RoutingBinder {
@Override
public void bind(RoutingBindingContext theRoutingBindingContext) {
theRoutingBindingContext.dependencies().use("myIndexStatus");
theRoutingBindingContext.bridge(TermConcept.class, new TermConceptBridge());
}
private class TermConceptBridge implements RoutingBridge<TermConcept> {
@Override
public void route(DocumentRoutes theDocumentRoutes, Object theO, TermConcept theTermConcept, RoutingBridgeRouteContext theRoutingBridgeRouteContext) {
if (theTermConcept.getIndexStatus() == null) {
theDocumentRoutes.notIndexed();
} else {
theDocumentRoutes.addRoute();
}
}
@Override
public void previousRoutes(DocumentRoutes theDocumentRoutes, Object theO, TermConcept theTermConcept, RoutingBridgeRouteContext theRoutingBridgeRouteContext) {
theDocumentRoutes.addRoute();
}
}
}

View File

@ -0,0 +1,91 @@
package ca.uhn.fhir.jpa.search;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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 com.sun.xml.bind.api.impl.NameConverter;
import org.apache.lucene.analysis.core.KeywordTokenizerFactory;
import org.apache.lucene.analysis.core.LowerCaseFilterFactory;
import org.apache.lucene.analysis.core.StopFilterFactory;
import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory;
import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory;
import org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory;
import org.apache.lucene.analysis.ngram.NGramFilterFactory;
import org.apache.lucene.analysis.pattern.PatternTokenizerFactory;
import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory;
import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;
import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
import org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurationContext;
import org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
* Factory for defining the analysers.
*/
@Component
public class HapiLuceneAnalysisConfigurer implements LuceneAnalysisConfigurer {
@Override
public void configure(LuceneAnalysisConfigurationContext theLuceneCtx) {
theLuceneCtx.analyzer("autocompleteEdgeAnalyzer").custom()
.tokenizer(PatternTokenizerFactory.class).param("pattern", "(.*)").param("group", "1")
.tokenFilter(LowerCaseFilterFactory.class)
.tokenFilter(StopFilterFactory.class)
.tokenFilter(EdgeNGramFilterFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "50");
theLuceneCtx.analyzer("autocompletePhoneticAnalyzer").custom()
.tokenizer(StandardTokenizerFactory.class)
.tokenFilter(StopFilterFactory.class)
.tokenFilter(PhoneticFilterFactory.class).param("encoder", "DoubleMetaphone")
.tokenFilter(SnowballPorterFilterFactory.class).param("language", "English");
theLuceneCtx.analyzer("autocompleteNGramAnalyzer").custom()
.tokenizer(StandardTokenizerFactory.class)
.tokenFilter(WordDelimiterFilterFactory.class)
.tokenFilter(LowerCaseFilterFactory.class)
.tokenFilter(NGramFilterFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "20");
theLuceneCtx.analyzer("autocompleteWordEdgeAnalyzer").custom()
.tokenizer(StandardTokenizerFactory.class)
.tokenFilter(LowerCaseFilterFactory.class)
.tokenFilter(StopFilterFactory.class)
.tokenFilter(EdgeNGramFilterFactory.class)
.param("minGramSize", "2")
.param("maxGramSize", "20");
theLuceneCtx.analyzer("standardAnalyzer").custom()
.tokenizer(StandardTokenizerFactory.class)
.tokenFilter(LowerCaseFilterFactory.class);
theLuceneCtx.analyzer("exactAnalyzer").custom()
.tokenizer(KeywordTokenizerFactory.class);
theLuceneCtx.analyzer("conceptParentPidsAnalyzer").custom()
.tokenizer(WhitespaceTokenizerFactory.class);
theLuceneCtx.analyzer("termConceptPropertyAnalyzer").custom()
.tokenizer(WhitespaceTokenizerFactory.class);
}
}

View File

@ -1,78 +0,0 @@
package ca.uhn.fhir.jpa.search;
/*-
* #%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 org.apache.lucene.analysis.core.*;
import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory;
import org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory;
import org.apache.lucene.analysis.ngram.NGramFilterFactory;
import org.apache.lucene.analysis.pattern.PatternTokenizerFactory;
import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory;
import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;
import org.apache.lucene.analysis.standard.StandardFilterFactory;
import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
import org.hibernate.search.annotations.Factory;
import org.hibernate.search.cfg.SearchMapping;
/**
* Factory for defining the analysers.
*/
public class LuceneSearchMappingFactory {
@Factory
public SearchMapping getSearchMapping() {
SearchMapping mapping = new SearchMapping();
mapping.analyzerDef("autocompleteEdgeAnalyzer", PatternTokenizerFactory.class)
.tokenizerParam("pattern", "(.*)")
.tokenizerParam("group", "1")
.filter(LowerCaseFilterFactory.class)
.filter(StopFilterFactory.class)
.filter(EdgeNGramFilterFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "50")
.analyzerDef("autocompleteWordEdgeAnalyzer", StandardTokenizerFactory.class)
.filter(LowerCaseFilterFactory.class)
.filter(StopFilterFactory.class)
.filter(EdgeNGramFilterFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "20")
.analyzerDef("autocompletePhoneticAnalyzer", StandardTokenizerFactory.class)
.filter(StandardFilterFactory.class)
.filter(StopFilterFactory.class)
.filter(PhoneticFilterFactory.class)
.param("encoder", "DoubleMetaphone")
.filter(SnowballPorterFilterFactory.class)
.param("language", "English")
.analyzerDef("autocompleteNGramAnalyzer", StandardTokenizerFactory.class)
.filter(WordDelimiterFilterFactory.class)
.filter(LowerCaseFilterFactory.class)
.filter(NGramFilterFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "20")
.analyzerDef("standardAnalyzer", StandardTokenizerFactory.class)
.filter(LowerCaseFilterFactory.class)
.analyzerDef("exactAnalyzer", KeywordTokenizerFactory.class)
.analyzerDef("conceptParentPidsAnalyzer", WhitespaceTokenizerFactory.class)
.analyzerDef("termConceptPropertyAnalyzer", WhitespaceTokenizerFactory.class);
return mapping;
}
}

View File

@ -31,7 +31,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
import ca.uhn.fhir.jpa.config.HibernateDialectProvider;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.IResultIterator;
@ -173,7 +173,7 @@ public class SearchBuilder implements ISearchBuilder {
@Autowired
private SqlObjectFactory mySqlBuilderFactory;
@Autowired
private HibernateDialectProvider myDialectProvider;
private HibernatePropertiesProvider myDialectProvider;
/**
* Constructor

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.jpa.util.SearchBox;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.dstu2.resource.Location;
import ca.uhn.fhir.rest.param.QuantityParam;
@ -35,6 +34,7 @@ import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.hibernate.search.engine.spatial.GeoBoundingBox;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -115,7 +115,7 @@ public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
double latitudeDegrees = Double.parseDouble(latitudeValue);
double longitudeDegrees = Double.parseDouble(longitudeValue);
SearchBox box = CoordCalculator.getBox(latitudeDegrees, longitudeDegrees, distanceKm);
GeoBoundingBox box = CoordCalculator.getBox(latitudeDegrees, longitudeDegrees, distanceKm);
latitudePredicate = theFrom.createLatitudePredicateFromBox(box);
longitudePredicate = theFrom.createLongitudePredicateFromBox(box);
}
@ -132,23 +132,23 @@ public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
return BinaryCondition.equalTo(myColumnLongitude, generatePlaceholder(theLongitudeValue));
}
public Condition createLatitudePredicateFromBox(SearchBox theBox) {
public Condition createLatitudePredicateFromBox(GeoBoundingBox theBox) {
return ComboCondition.and(
BinaryCondition.greaterThanOrEq(myColumnLatitude, generatePlaceholder(theBox.getSouthWest().getLatitude())),
BinaryCondition.lessThanOrEq(myColumnLatitude, generatePlaceholder(theBox.getNorthEast().getLatitude()))
BinaryCondition.greaterThanOrEq(myColumnLatitude, generatePlaceholder(theBox.bottomRight().latitude())),
BinaryCondition.lessThanOrEq(myColumnLatitude, generatePlaceholder(theBox.topLeft().latitude()))
);
}
public Condition createLongitudePredicateFromBox(SearchBox theBox) {
if (theBox.crossesAntiMeridian()) {
public Condition createLongitudePredicateFromBox(GeoBoundingBox theBox) {
if (theBox.bottomRight().longitude() < theBox.topLeft().longitude()) {
return ComboCondition.or(
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.getNorthEast().getLongitude())),
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.getSouthWest().getLongitude()))
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.bottomRight().longitude())),
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.topLeft().longitude()))
);
}
return ComboCondition.and(
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.getSouthWest().getLongitude())),
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.getNorthEast().getLongitude()))
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.topLeft().longitude())),
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.bottomRight().longitude()))
);
}
}

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.search.builder.sql;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.config.HibernateDialectProvider;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
@ -101,7 +101,7 @@ public class SearchQueryBuilder {
/**
* Constructor
*/
public SearchQueryBuilder(FhirContext theFhirContext, ModelConfig theModelConfig, PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, SqlObjectFactory theSqlBuilderFactory, HibernateDialectProvider theDialectProvider, boolean theCountQuery) {
public SearchQueryBuilder(FhirContext theFhirContext, ModelConfig theModelConfig, PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, SqlObjectFactory theSqlBuilderFactory, HibernatePropertiesProvider theDialectProvider, boolean theCountQuery) {
this(theFhirContext, theModelConfig, thePartitionSettings, theRequestPartitionId, theResourceType, theSqlBuilderFactory, UUID.randomUUID().toString() + "-", theDialectProvider.getDialect(), theCountQuery, new ArrayList<>());
}

View File

@ -20,29 +20,48 @@ package ca.uhn.fhir.jpa.search.elastic;
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchRestClientFactory;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.elasticsearch.cfg.ElasticsearchEnvironment;
import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus;
import org.hibernate.search.elasticsearch.cfg.IndexSchemaManagementStrategy;
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.search.backend.elasticsearch.index.IndexStatus;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategyNames;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchBackendSettings;
import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexSettings;
import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName;
import org.slf4j.Logger;
import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;
import static org.slf4j.LoggerFactory.getLogger;
/**
* This class is used to inject appropriate properties into a hibernate
* Properties object being used to create an entitymanager for a HAPI
* FHIR JPA server.
* FHIR JPA server. This class also injects a starter template into the ES cluster.
*/
public class ElasticsearchHibernatePropertiesBuilder {
private static final Logger ourLog = getLogger(ElasticsearchHibernatePropertiesBuilder.class);
private IndexStatus myRequiredIndexStatus = IndexStatus.YELLOW.YELLOW;
private SchemaManagementStrategyName myIndexSchemaManagementStrategy = SchemaManagementStrategyName.CREATE;
private ElasticsearchIndexStatus myRequiredIndexStatus = ElasticsearchIndexStatus.YELLOW;
private String myRestUrl;
private String myUsername;
private String myPassword;
private IndexSchemaManagementStrategy myIndexSchemaManagementStrategy = IndexSchemaManagementStrategy.CREATE;
private long myIndexManagementWaitTimeoutMillis = 10000L;
private boolean myDebugRefreshAfterWrite = false;
private String myDebugSyncStrategy = AutomaticIndexingSynchronizationStrategyNames.ASYNC;
private boolean myDebugPrettyPrintJsonLog = false;
private String myProtocol;
public ElasticsearchHibernatePropertiesBuilder setUsername(String theUsername) {
myUsername = theUsername;
@ -56,36 +75,35 @@ public class ElasticsearchHibernatePropertiesBuilder {
public void apply(Properties theProperties) {
// Don't use the Lucene properties as they conflict
theProperties.remove("hibernate.search.model_mapping");
// the below properties are used for ElasticSearch integration
theProperties.put("hibernate.search.default." + Environment.INDEX_MANAGER_IMPL_NAME, "elasticsearch");
theProperties.put("hibernate.search." + ElasticsearchEnvironment.ANALYSIS_DEFINITION_PROVIDER, ElasticsearchMappingProvider.class.getName());
theProperties.put(BackendSettings.backendKey(BackendSettings.TYPE), "elasticsearch");
theProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.ANALYSIS_CONFIGURER), HapiElasticsearchAnalysisConfigurer.class.getName());
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.HOSTS), myRestUrl);
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.PROTOCOL), myProtocol);
theProperties.put("hibernate.search.default.elasticsearch.host", myRestUrl);
if (StringUtils.isNotBlank(myUsername)) {
theProperties.put("hibernate.search.default.elasticsearch.username", myUsername);
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.USERNAME), myUsername);
}
if (StringUtils.isNotBlank(myPassword)) {
theProperties.put("hibernate.search.default.elasticsearch.password", myPassword);
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.PASSWORD), myPassword);
}
theProperties.put("hibernate.search.default." + ElasticsearchEnvironment.INDEX_SCHEMA_MANAGEMENT_STRATEGY, myIndexSchemaManagementStrategy.getExternalName());
theProperties.put("hibernate.search.default." + ElasticsearchEnvironment.INDEX_MANAGEMENT_WAIT_TIMEOUT, Long.toString(myIndexManagementWaitTimeoutMillis));
theProperties.put("hibernate.search.default." + ElasticsearchEnvironment.REQUIRED_INDEX_STATUS, myRequiredIndexStatus.getElasticsearchString());
theProperties.put(HibernateOrmMapperSettings.SCHEMA_MANAGEMENT_STRATEGY, myIndexSchemaManagementStrategy.externalRepresentation());
theProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS_WAIT_TIMEOUT), Long.toString(myIndexManagementWaitTimeoutMillis));
theProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS), myRequiredIndexStatus.externalRepresentation());
// Need the mapping to be dynamic because of terminology indexes.
theProperties.put("hibernate.search.default.elasticsearch.dynamic_mapping", "true");
theProperties.put(BackendSettings.backendKey(ElasticsearchIndexSettings.DYNAMIC_MAPPING), "true");
// Only for unit tests
theProperties.put("hibernate.search.default." + ElasticsearchEnvironment.REFRESH_AFTER_WRITE, Boolean.toString(myDebugRefreshAfterWrite));
theProperties.put("hibernate.search." + ElasticsearchEnvironment.LOG_JSON_PRETTY_PRINTING, Boolean.toString(myDebugPrettyPrintJsonLog));
theProperties.put(HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, myDebugSyncStrategy);
theProperties.put(BackendSettings.backendKey(ElasticsearchBackendSettings.LOG_JSON_PRETTY_PRINTING), Boolean.toString(myDebugPrettyPrintJsonLog));
injectStartupTemplate(myProtocol, myRestUrl, myUsername, myPassword);
}
public ElasticsearchHibernatePropertiesBuilder setRequiredIndexStatus(ElasticsearchIndexStatus theRequiredIndexStatus) {
public ElasticsearchHibernatePropertiesBuilder setRequiredIndexStatus(IndexStatus theRequiredIndexStatus) {
myRequiredIndexStatus = theRequiredIndexStatus;
return this;
}
@ -95,7 +113,12 @@ public class ElasticsearchHibernatePropertiesBuilder {
return this;
}
public ElasticsearchHibernatePropertiesBuilder setIndexSchemaManagementStrategy(IndexSchemaManagementStrategy theIndexSchemaManagementStrategy) {
public ElasticsearchHibernatePropertiesBuilder setProtocol(String theProtocol) {
myProtocol = theProtocol;
return this;
}
public ElasticsearchHibernatePropertiesBuilder setIndexSchemaManagementStrategy(SchemaManagementStrategyName theIndexSchemaManagementStrategy) {
myIndexSchemaManagementStrategy = theIndexSchemaManagementStrategy;
return this;
}
@ -105,15 +128,40 @@ public class ElasticsearchHibernatePropertiesBuilder {
return this;
}
public ElasticsearchHibernatePropertiesBuilder setDebugRefreshAfterWrite(boolean theDebugRefreshAfterWrite) {
myDebugRefreshAfterWrite = theDebugRefreshAfterWrite;
public ElasticsearchHibernatePropertiesBuilder setDebugIndexSyncStrategy(String theSyncStrategy) {
myDebugSyncStrategy = theSyncStrategy;
return this;
}
public ElasticsearchHibernatePropertiesBuilder setDebugPrettyPrintJsonLog(boolean theDebugPrettyPrintJsonLog) {
myDebugPrettyPrintJsonLog = theDebugPrettyPrintJsonLog;
return this;
}
/**
* At startup time, injects a template into the elasticsearch cluster, which is needed for handling large ngram diffs.
* 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.
*/
private void injectStartupTemplate(String theProtocol, String theHostAndPort, String theUsername, String thePassword) {
PutIndexTemplateRequest ngramTemplate = new PutIndexTemplateRequest("ngram-template")
.patterns(Arrays.asList("resourcetable-*", "termconcept-*"))
.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 {
RestHighLevelClient elasticsearchHighLevelRestClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(qualifiedHost, port, theUsername, thePassword);
ourLog.info("Adding starter template for large ngram diffs");
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.");
}
}
}

View File

@ -1,69 +0,0 @@
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 org.hibernate.search.elasticsearch.analyzer.definition.ElasticsearchAnalysisDefinitionProvider;
import org.hibernate.search.elasticsearch.analyzer.definition.ElasticsearchAnalysisDefinitionRegistryBuilder;
public class ElasticsearchMappingProvider implements ElasticsearchAnalysisDefinitionProvider {
@Override
public void register(ElasticsearchAnalysisDefinitionRegistryBuilder builder) {
builder.analyzer("autocompleteEdgeAnalyzer")
.withTokenizer("pattern_all")
.withTokenFilters("lowercase", "stop", "edgengram_3_50");
builder.tokenizer("pattern_all").type("pattern").param("pattern", "(.*)").param("group", "1");
builder.tokenFilter("edgengram_3_50")
.type("edgeNGram")
.param("min_gram", "3")
.param("max_gram", "50");
builder.analyzer("autocompleteWordEdgeAnalyzer")
.withTokenizer("standard")
.withTokenFilters("lowercase", "stop", "wordedgengram_3_50");
builder.tokenFilter("wordedgengram_3_50")
.type("edgeNGram")
.param("min_gram", "3")
.param("max_gram", "20");
builder.analyzer("autocompletePhoneticAnalyzer")
.withTokenizer("standard")
.withTokenFilters("standard", "stop", "snowball_english");
builder.tokenFilter("snowball_english").type("snowball").param("language", "English");
builder.analyzer("autocompleteNGramAnalyzer")
.withTokenizer("standard")
.withTokenFilters("word_delimiter", "lowercase", "ngram_3_20");
builder.tokenFilter("ngram_3_20")
.type("nGram")
.param("min_gram", "3")
.param("max_gram", "20");
builder.analyzer("standardAnalyzer").withTokenizer("standard").withTokenFilters("lowercase");
builder.analyzer("exactAnalyzer").withTokenizer("keyword");
builder.analyzer("conceptParentPidsAnalyzer").withTokenizer("whitespace");
builder.analyzer("termConceptPropertyAnalyzer").withTokenizer("whitespace");
}
}

View File

@ -0,0 +1,87 @@
package ca.uhn.fhir.jpa.search.elastic;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurationContext;
import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer;
public class HapiElasticsearchAnalysisConfigurer implements ElasticsearchAnalysisConfigurer{
@Override
public void configure(ElasticsearchAnalysisConfigurationContext theConfigCtx) {
theConfigCtx.analyzer("autocompleteEdgeAnalyzer").custom()
.tokenizer("pattern_all")
.tokenFilters("lowercase", "stop", "edgengram_3_50");
theConfigCtx.tokenizer("pattern_all")
.type("pattern")
.param("pattern", "(.*)")
.param("group", "1");
theConfigCtx.tokenFilter("edgengram_3_50")
.type("edgeNGram")
.param("min_gram", "3")
.param("max_gram", "50");
theConfigCtx.analyzer("autocompleteWordEdgeAnalyzer").custom()
.tokenizer("standard")
.tokenFilters("lowercase", "stop", "wordedgengram_3_50");
theConfigCtx.tokenFilter("wordedgengram_3_50")
.type("edgeNGram")
.param("min_gram", "2")
.param("max_gram", "20");
theConfigCtx.analyzer("autocompletePhoneticAnalyzer").custom()
.tokenizer("standard")
.tokenFilters("stop", "snowball_english");
theConfigCtx.tokenFilter("snowball_english")
.type("snowball")
.param("language", "English");
theConfigCtx.analyzer("autocompleteNGramAnalyzer").custom()
.tokenizer("standard")
.tokenFilters("word_delimiter", "lowercase", "ngram_3_20");
theConfigCtx.tokenFilter("ngram_3_20")
.type("nGram")
.param("min_gram", "3")
.param("max_gram", "20");
theConfigCtx.analyzer("standardAnalyzer").custom()
.tokenizer("standard")
.tokenFilters("lowercase");
theConfigCtx.analyzer("exactAnalyzer")
.custom()
.tokenizer("keyword");
theConfigCtx.analyzer("conceptParentPidsAnalyzer").custom()
.tokenizer("whitespace");
theConfigCtx.analyzer("termConceptPropertyAnalyzer").custom()
.tokenizer("whitespace");
}
}

View File

@ -27,20 +27,38 @@ import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.message.BasicHeader;
import org.shadehapi.elasticsearch.client.RestClient;
import org.shadehapi.elasticsearch.client.RestClientBuilder;
import org.shadehapi.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
public class ElasticsearchRestClientFactory {
private static String determineScheme(String theHostname) {
int schemeIdx = theHostname.indexOf("://");
if (schemeIdx > 0) {
return theHostname.substring(0, schemeIdx);
} else {
return "http";
}
}
private static String stripHostOfScheme(String theHostname) {
int schemeIdx = theHostname.indexOf("://");
if (schemeIdx > 0) {
return theHostname.substring(schemeIdx + 3);
} else {
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(theHostname, thePort))
new HttpHost(stripHostOfScheme(theHostname), thePort, determineScheme(theHostname)))
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider));

View File

@ -37,40 +37,40 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import org.shadehapi.elasticsearch.action.DocWriteResponse;
import org.shadehapi.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.shadehapi.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.shadehapi.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.shadehapi.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.shadehapi.elasticsearch.action.index.IndexRequest;
import org.shadehapi.elasticsearch.action.index.IndexResponse;
import org.shadehapi.elasticsearch.action.search.SearchRequest;
import org.shadehapi.elasticsearch.action.search.SearchResponse;
import org.shadehapi.elasticsearch.client.RequestOptions;
import org.shadehapi.elasticsearch.client.RestHighLevelClient;
import org.shadehapi.elasticsearch.common.xcontent.XContentType;
import org.shadehapi.elasticsearch.index.query.BoolQueryBuilder;
import org.shadehapi.elasticsearch.index.query.MatchQueryBuilder;
import org.shadehapi.elasticsearch.index.query.QueryBuilders;
import org.shadehapi.elasticsearch.index.query.RangeQueryBuilder;
import org.shadehapi.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.shadehapi.elasticsearch.search.SearchHit;
import org.shadehapi.elasticsearch.search.SearchHits;
import org.shadehapi.elasticsearch.search.aggregations.AggregationBuilder;
import org.shadehapi.elasticsearch.search.aggregations.AggregationBuilders;
import org.shadehapi.elasticsearch.search.aggregations.Aggregations;
import org.shadehapi.elasticsearch.search.aggregations.BucketOrder;
import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.CompositeAggregationBuilder;
import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.ParsedComposite;
import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.ParsedTerms;
import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.shadehapi.elasticsearch.search.aggregations.metrics.tophits.ParsedTopHits;
import org.shadehapi.elasticsearch.search.aggregations.support.ValueType;
import org.shadehapi.elasticsearch.search.builder.SearchSourceBuilder;
import org.shadehapi.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.ParsedComposite;
import org.elasticsearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedTopHits;
import org.elasticsearch.search.aggregations.support.ValueType;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.BufferedReader;
@ -120,9 +120,16 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
private final RestHighLevelClient myRestHighLevelClient;
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private PartitionSettings myPartitionSettings;
//This constructor used to inject a dummy partitionsettings in test.
public ElasticsearchSvcImpl(PartitionSettings thePartitionSetings, String theHostname, int thePort, String theUsername, String thePassword) {
this(theHostname, thePort, theUsername, thePassword);
this.myPartitionSettings = thePartitionSetings;
}
public ElasticsearchSvcImpl(String theHostname, int thePort, String theUsername, String thePassword) {
myRestHighLevelClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(theHostname, thePort, theUsername, thePassword);
@ -176,8 +183,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
}
private boolean indexExists(String theIndexName) throws IOException {
GetIndexRequest request = new GetIndexRequest();
request.indices(theIndexName);
GetIndexRequest request = new GetIndexRequest(theIndexName);
return myRestHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
}
@ -278,14 +284,14 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
}
private TermsAggregationBuilder createObservationCodeAggregationBuilder(int theMaxNumberObservationsPerCode, String[] theTopHitsInclude) {
TermsAggregationBuilder observationCodeCodeAggregationBuilder = new TermsAggregationBuilder(GROUP_BY_CODE, ValueType.STRING).field(OBSERVATION_CODEVALUE_FIELD_NAME);
TermsAggregationBuilder observationCodeCodeAggregationBuilder = new TermsAggregationBuilder(GROUP_BY_CODE).field(OBSERVATION_CODEVALUE_FIELD_NAME);
observationCodeCodeAggregationBuilder.order(BucketOrder.key(true));
// Top Hits Aggregation
observationCodeCodeAggregationBuilder.subAggregation(AggregationBuilders.topHits(MOST_RECENT_EFFECTIVE)
.sort(OBSERVATION_EFFECTIVEDTM_FIELD_NAME, SortOrder.DESC)
.fetchSource(theTopHitsInclude, null).size(theMaxNumberObservationsPerCode));
observationCodeCodeAggregationBuilder.size(10000);
TermsAggregationBuilder observationCodeSystemAggregationBuilder = new TermsAggregationBuilder(GROUP_BY_SYSTEM, ValueType.STRING).field(OBSERVATION_CODESYSTEM_FIELD_NAME);
TermsAggregationBuilder observationCodeSystemAggregationBuilder = new TermsAggregationBuilder(GROUP_BY_SYSTEM).field(OBSERVATION_CODESYSTEM_FIELD_NAME);
observationCodeSystemAggregationBuilder.order(BucketOrder.key(true));
observationCodeSystemAggregationBuilder.subAggregation(observationCodeCodeAggregationBuilder);
return observationCodeSystemAggregationBuilder;
@ -728,8 +734,6 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
private IndexRequest createIndexRequest(String theIndexName, String theDocumentId, String theObservationDocument, String theDocumentType) {
IndexRequest request = new IndexRequest(theIndexName);
request.id(theDocumentId);
request.type(theDocumentType);
request.source(theObservationDocument, XContentType.JSON);
return request;
}

View File

@ -46,7 +46,6 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.search.util.impl.Executors;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.InstantType;
import org.quartz.JobExecutionContext;
@ -165,7 +164,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
public void initExecutor() {
// Create the threadpool executor used for reindex jobs
int reindexThreadCount = myDaoConfig.getReindexThreadCount();
RejectedExecutionHandler rejectHandler = new Executors.BlockPolicy();
RejectedExecutionHandler rejectHandler = new BlockPolicy();
myTaskExecutor = new ThreadPoolExecutor(0, reindexThreadCount,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(100),
@ -173,6 +172,30 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
rejectHandler
);
}
/**
* A handler for rejected tasks that will have the caller block until space is available.
* This was stolen from old hibernate search(5.X.X), as it has been removed in HS6. We can probably come up with a better solution though.
*/
public static class BlockPolicy implements RejectedExecutionHandler {
/**
* Puts the Runnable to the blocking queue, effectively blocking the delegating thread until space is available.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
try {
e.getQueue().put( r );
}
catch (InterruptedException e1) {
ourLog.error("Interrupted Execption for task: {}",r, e1 );
Thread.currentThread().interrupt();
}
}
}
public void scheduleJob() {
ScheduledJobDefinition jobDetail = new ScheduledJobDefinition();
@ -545,6 +568,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
}
doReindex(resourceTable, resource);
return null;
} catch (Exception e) {

View File

@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.model.TranslationQuery;
import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
@ -56,7 +57,7 @@ import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.entity.TermConceptProperty;
import ca.uhn.fhir.jpa.entity.TermConceptPropertyFieldBridge;
import ca.uhn.fhir.jpa.entity.TermConceptPropertyBinder;
import ca.uhn.fhir.jpa.entity.TermConceptPropertyTypeEnum;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
@ -98,18 +99,17 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.TermQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.backend.elasticsearch.ElasticsearchExtension;
import org.hibernate.search.backend.lucene.LuceneExtension;
import org.hibernate.search.engine.search.predicate.dsl.BooleanPredicateClausesStep;
import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep;
import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory;
import org.hibernate.search.engine.search.query.SearchQuery;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.exceptions.FHIRException;
@ -252,6 +252,14 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
private volatile IValidationSupport myJpaValidationSupport;
private volatile IValidationSupport myValidationSupport;
//We need this bean so we can tell which mode hibernate search is running in.
@Autowired
private HibernatePropertiesProvider myHibernatePropertiesProvider;
private boolean isFullTextSetToUseElastic() {
return "elasticsearch".equalsIgnoreCase(myHibernatePropertiesProvider.getHibernateSearchBackend());
}
@Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
TermCodeSystemVersion cs = getCurrentCodeSystemVersion(theSystem);
@ -538,6 +546,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
conceptViews = myTermValueSetConceptViewDao.findByTermValueSetId(theTermValueSet.getId(), displayValue);
wasFilteredResult = true;
} else {
// TODO JA HS: I'm pretty sure we are overfetching here. test says offset 3, count 4, but we are fetching index 3 -> 10 here, grabbing 7 concepts.
//Specifically this test testExpandInline_IncludePreExpandedValueSetByUri_FilterOnDisplay_LeftMatch_SelectRange
conceptViews = myTermValueSetConceptViewDao.findByTermValueSetId(offset, toIndex, theTermValueSet.getId());
theAccumulator.consumeSkipCount(offset);
if (theAdd) {
@ -899,8 +909,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} else {
csv = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(theCs.getPid(), includeOrExcludeVersion);
}
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
SearchSession searchSession = Search.session(myEntityManager);
/*
* If FullText searching is not enabled, we can handle only basic expansions
* since we're going to do it without the database.
@ -913,68 +923,41 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
/*
* Ok, let's use hibernate search to build the expansion
*/
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(TermConcept.class).get();
BooleanJunction<?> bool = qb.bool();
//Manually building a predicate since we need to throw it around.
SearchPredicateFactory predicate = searchSession.scope(TermConcept.class).predicate();
bool.must(qb.keyword().onField("myCodeSystemVersionPid").matching(csv.getPid()).createQuery());
//Build the top-level expansion on filters.
PredicateFinalStep step = predicate.bool(b -> {
b.must(predicate.match().field("myCodeSystemVersionPid").matching(csv.getPid()));
if (theExpansionFilter.hasCode()) {
bool.must(qb.keyword().onField("myCode").matching(theExpansionFilter.getCode()).createQuery());
}
/*
* Filters
*/
String codeSystemUrlAndVersion;
if (includeOrExcludeVersion != null) {
codeSystemUrlAndVersion = theSystem + "|" + includeOrExcludeVersion;
} else {
codeSystemUrlAndVersion = theSystem;
}
for (ValueSet.ConceptSetFilterComponent nextFilter : theIncludeOrExclude.getFilter()) {
handleFilter(codeSystemUrlAndVersion, qb, bool, nextFilter);
}
for (ValueSet.ConceptSetFilterComponent nextFilter : theExpansionFilter.getFilters()) {
handleFilter(codeSystemUrlAndVersion, qb, bool, nextFilter);
}
Query luceneQuery = bool.createQuery();
/*
* Include/Exclude Concepts
*/
List<Term> codes = theIncludeOrExclude
.getConcept()
.stream()
.filter(Objects::nonNull)
.map(ValueSet.ConceptReferenceComponent::getCode)
.filter(StringUtils::isNotBlank)
.map(t -> new Term("myCode", t))
.collect(Collectors.toList());
if (codes.size() > 0) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.setMinimumNumberShouldMatch(1);
for (Term nextCode : codes) {
builder.add(new TermQuery(nextCode), BooleanClause.Occur.SHOULD);
if (theExpansionFilter.hasCode()) {
b.must(predicate.match().field("myCode").matching(theExpansionFilter.getCode()));
}
luceneQuery = new BooleanQuery.Builder()
.add(luceneQuery, BooleanClause.Occur.MUST)
.add(builder.build(), BooleanClause.Occur.MUST)
.build();
}
String codeSystemUrlAndVersion = buildCodeSystemUrlAndVersion(theSystem, includeOrExcludeVersion);
for (ValueSet.ConceptSetFilterComponent nextFilter : theIncludeOrExclude.getFilter()) {
handleFilter(codeSystemUrlAndVersion, predicate, b, nextFilter);
}
for (ValueSet.ConceptSetFilterComponent nextFilter : theExpansionFilter.getFilters()) {
handleFilter(codeSystemUrlAndVersion, predicate, b, nextFilter);
}
});
/*
* Execute the query
*/
FullTextQuery jpaQuery = em.createFullTextQuery(luceneQuery, TermConcept.class);
PredicateFinalStep expansionStep = buildExpansionPredicate(theIncludeOrExclude, predicate);
final PredicateFinalStep finishedQuery;
if (expansionStep == null) {
finishedQuery = step;
} else {
finishedQuery = predicate.bool().must(step).must(expansionStep);
}
/*
* DM 2019-08-21 - Processing slows after any ValueSets with many codes explicitly identified. This might
* be due to the dark arts that is memory management. Will monitor but not do anything about this right now.
*/
BooleanQuery.setMaxClauseCount(SearchBuilder.getMaximumPageSize());
//BooleanQuery.setMaxClauseCount(SearchBuilder.getMaximumPageSize());
//TODO GGG HS looks like we can't set max clause count, but it can be set server side.
//BooleanQuery.setMaxClauseCount(10000);
StopWatch sw = new StopWatch();
AtomicInteger count = new AtomicInteger(0);
@ -995,22 +978,27 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
}
jpaQuery.setMaxResults(maxResultsPerBatch);
jpaQuery.setFirstResult(theQueryIndex * maxResultsPerBatch);
// jpaQuery.setMaxResults(maxResultsPerBatch);
// jpaQuery.setFirstResult(theQueryIndex * maxResultsPerBatch);
ourLog.debug("Beginning batch expansion for {} with max results per batch: {}", (theAdd ? "inclusion" : "exclusion"), maxResultsPerBatch);
StopWatch swForBatch = new StopWatch();
AtomicInteger countForBatch = new AtomicInteger(0);
List<?> resultList = jpaQuery.getResultList();
int resultsInBatch = resultList.size();
int firstResult = jpaQuery.getFirstResult();
SearchQuery<TermConcept> termConceptsQuery = searchSession.search(TermConcept.class)
.where(f -> finishedQuery).toQuery();
System.out.println("About to query:" + termConceptsQuery.queryString());
List<TermConcept> termConcepts = termConceptsQuery.fetchHits(theQueryIndex * maxResultsPerBatch, maxResultsPerBatch);
int resultsInBatch = termConcepts.size();
int firstResult = theQueryIndex * maxResultsPerBatch;// TODO GGG HS we lose the ability to check the index of the first result, so just best-guessing it here.
int delta = 0;
for (Object next : resultList) {
for (TermConcept concept: termConcepts) {
count.incrementAndGet();
countForBatch.incrementAndGet();
TermConcept concept = (TermConcept) next;
boolean added = addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, concept, theAdd, includeOrExcludeVersion);
if (added) {
delta++;
@ -1028,6 +1016,46 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
}
/**
* Helper method which builds a predicate for the expansion
*/
private PredicateFinalStep buildExpansionPredicate(ValueSet.ConceptSetComponent theTheIncludeOrExclude, SearchPredicateFactory thePredicate) {
PredicateFinalStep expansionStep;
/*
* Include/Exclude Concepts
*/
List<Term> codes = theTheIncludeOrExclude
.getConcept()
.stream()
.filter(Objects::nonNull)
.map(ValueSet.ConceptReferenceComponent::getCode)
.filter(StringUtils::isNotBlank)
.map(t -> new Term("myCode", t))
.collect(Collectors.toList());
if (codes.size() > 0) {
expansionStep = thePredicate.bool(b -> {
b.minimumShouldMatchNumber(1);
for (Term code : codes) {
b.should(thePredicate.match().field(code.field()).matching(code.text()));
}
});
return expansionStep;
} else {
return null;
}
}
private String buildCodeSystemUrlAndVersion(String theSystem, String theIncludeOrExcludeVersion) {
String codeSystemUrlAndVersion;
if (theIncludeOrExcludeVersion != null) {
codeSystemUrlAndVersion = theSystem + "|" + theIncludeOrExcludeVersion;
} else {
codeSystemUrlAndVersion = theSystem;
}
return codeSystemUrlAndVersion;
}
private @Nonnull
ValueSetExpansionOptions provideExpansionOptions(@Nullable ValueSetExpansionOptions theExpansionOptions) {
if (theExpansionOptions != null) {
@ -1046,7 +1074,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
}
private void handleFilter(String theCodeSystemIdentifier, QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
private void handleFilter(String theCodeSystemIdentifier, SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB, ValueSet.ConceptSetFilterComponent theFilter) {
if (isBlank(theFilter.getValue()) && theFilter.getOp() == null && isBlank(theFilter.getProperty())) {
return;
}
@ -1058,258 +1086,36 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
switch (theFilter.getProperty()) {
case "display:exact":
case "display":
handleFilterDisplay(theQb, theBool, theFilter);
handleFilterDisplay(theF, theB, theFilter);
break;
case "concept":
case "code":
handleFilterConceptAndCode(theCodeSystemIdentifier, theQb, theBool, theFilter);
handleFilterConceptAndCode(theCodeSystemIdentifier, theF, theB, theFilter);
break;
case "parent":
case "child":
isCodeSystemLoincOrThrowInvalidRequestException(theCodeSystemIdentifier, theFilter.getProperty());
handleFilterLoincParentChild(theBool, theFilter);
handleFilterLoincParentChild(theF, theB, theFilter);
break;
case "ancestor":
isCodeSystemLoincOrThrowInvalidRequestException(theCodeSystemIdentifier, theFilter.getProperty());
handleFilterLoincAncestor(theCodeSystemIdentifier, theBool, theFilter);
handleFilterLoincAncestor2(theCodeSystemIdentifier, theF, theB, theFilter);
break;
case "descendant":
isCodeSystemLoincOrThrowInvalidRequestException(theCodeSystemIdentifier, theFilter.getProperty());
handleFilterLoincDescendant(theCodeSystemIdentifier, theBool, theFilter);
handleFilterLoincDescendant(theCodeSystemIdentifier, theF, theB, theFilter);
break;
case "copyright":
isCodeSystemLoincOrThrowInvalidRequestException(theCodeSystemIdentifier, theFilter.getProperty());
handleFilterLoincCopyright(theBool, theFilter);
handleFilterLoincCopyright(theF, theB, theFilter);
break;
default:
handleFilterRegex(theBool, theFilter);
handleFilterRegex(theF, theB, theFilter);
break;
}
}
private void isCodeSystemLoincOrThrowInvalidRequestException(String theSystemIdentifier, String theProperty) {
String systemUrl = getUrlFromIdentifier(theSystemIdentifier);
if (!isCodeSystemLoinc(systemUrl)) {
throw new InvalidRequestException("Invalid filter, property " + theProperty + " is LOINC-specific and cannot be used with system: " + systemUrl);
}
}
private boolean isCodeSystemLoinc(String theSystem) {
return ITermLoaderSvc.LOINC_URI.equals(theSystem);
}
private void handleFilterDisplay(QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
if (theFilter.getProperty().equals("display:exact") && theFilter.getOp() == ValueSet.FilterOperator.EQUAL) {
addDisplayFilterExact(theQb, theBool, theFilter);
} else if (theFilter.getProperty().equals("display") && theFilter.getOp() == ValueSet.FilterOperator.EQUAL) {
if (theFilter.getValue().trim().contains(" ")) {
addDisplayFilterExact(theQb, theBool, theFilter);
} else {
addDisplayFilterInexact(theQb, theBool, theFilter);
}
}
}
private void addDisplayFilterExact(QueryBuilder qb, BooleanJunction<?> bool, ValueSet.ConceptSetFilterComponent nextFilter) {
bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery());
}
private void addDisplayFilterInexact(QueryBuilder qb, BooleanJunction<?> bool, ValueSet.ConceptSetFilterComponent nextFilter) {
Query textQuery = qb
.phrase()
.withSlop(2)
.onField("myDisplay").boostedTo(4.0f)
//.andField("myDisplayEdgeNGram").boostedTo(2.0f)
.andField("myDisplayWordEdgeNGram").boostedTo(1.0f)
// .andField("myDisplayPhonetic").boostedTo(0.5f)
.sentence(nextFilter.getValue().toLowerCase()).createQuery();
bool.must(textQuery);
}
private void handleFilterConceptAndCode(String theSystem, QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
TermConcept code = findCode(theSystem, theFilter.getValue())
.orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theFilter.getValue()));
if (theFilter.getOp() == ValueSet.FilterOperator.ISA) {
ourLog.debug(" * Filtering on codes with a parent of {}/{}/{}", code.getId(), code.getCode(), code.getDisplay());
theBool.must(theQb.keyword().onField("myParentPids").matching("" + code.getId()).createQuery());
} else {
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
}
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincParentChild(BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
case EQUAL:
addLoincFilterParentChildEqual(theBool, theFilter.getProperty(), theFilter.getValue());
break;
case IN:
addLoincFilterParentChildIn(theBool, theFilter);
break;
default:
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
}
}
private void addLoincFilterParentChildEqual(BooleanJunction<?> theBool, String theProperty, String theValue) {
logFilteringValueOnProperty(theValue, theProperty);
theBool.must(new TermsQuery(getPropertyTerm(theProperty, theValue)));
}
private void addLoincFilterParentChildIn(BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(",");
List<Term> terms = new ArrayList<>();
for (String value : values) {
logFilteringValueOnProperty(value, theFilter.getProperty());
terms.add(getPropertyTerm(theFilter.getProperty(), value));
}
theBool.must(new TermsQuery(terms));
}
private Term getPropertyTerm(String theProperty, String theValue) {
return new Term(TermConceptPropertyFieldBridge.CONCEPT_FIELD_PROPERTY_PREFIX + theProperty, theValue);
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincAncestor(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
case EQUAL:
addLoincFilterAncestorEqual(theSystem, theBool, theFilter);
break;
case IN:
addLoincFilterAncestorIn(theSystem, theBool, theFilter);
break;
default:
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
}
}
private void addLoincFilterAncestorEqual(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
addLoincFilterAncestorEqual(theSystem, theBool, theFilter.getProperty(), theFilter.getValue());
}
private void addLoincFilterAncestorEqual(String theSystem, BooleanJunction<?> theBool, String theProperty, String theValue) {
List<Term> terms = getAncestorTerms(theSystem, theProperty, theValue);
theBool.must(new TermsQuery(terms));
}
private void addLoincFilterAncestorIn(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(",");
List<Term> terms = new ArrayList<>();
for (String value : values) {
terms.addAll(getAncestorTerms(theSystem, theFilter.getProperty(), value));
}
theBool.must(new TermsQuery(terms));
}
private List<Term> getAncestorTerms(String theSystem, String theProperty, String theValue) {
List<Term> retVal = new ArrayList<>();
TermConcept code = findCode(theSystem, theValue)
.orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theValue));
retVal.add(new Term("myParentPids", "" + code.getId()));
logFilteringValueOnProperty(theValue, theProperty);
return retVal;
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincDescendant(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
case EQUAL:
addLoincFilterDescendantEqual(theSystem, theBool, theFilter);
break;
case IN:
addLoincFilterDescendantIn(theSystem, theBool, theFilter);
break;
default:
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
}
}
private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
addLoincFilterDescendantEqual(theSystem, theBool, theFilter.getProperty(), theFilter.getValue());
}
private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction<?> theBool, String theProperty, String theValue) {
List<Term> terms = getDescendantTerms(theSystem, theProperty, theValue);
theBool.must(new TermsQuery(terms));
}
private void addLoincFilterDescendantIn(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(",");
List<Term> terms = new ArrayList<>();
for (String value : values) {
terms.addAll(getDescendantTerms(theSystem, theFilter.getProperty(), value));
}
theBool.must(new TermsQuery(terms));
}
private List<Term> getDescendantTerms(String theSystem, String theProperty, String theValue) {
List<Term> retVal = new ArrayList<>();
TermConcept code = findCode(theSystem, theValue)
.orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theValue));
String[] parentPids = code.getParentPidsAsString().split(" ");
for (String parentPid : parentPids) {
retVal.add(new Term("myId", parentPid));
}
logFilteringValueOnProperty(theValue, theProperty);
return retVal;
}
private void handleFilterLoincCopyright(BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
if (theFilter.getOp() == ValueSet.FilterOperator.EQUAL) {
String copyrightFilterValue = defaultString(theFilter.getValue()).toLowerCase();
switch (copyrightFilterValue) {
case "3rdparty":
logFilteringValueOnProperty(theFilter.getValue(), theFilter.getProperty());
addFilterLoincCopyright3rdParty(theBool);
break;
case "loinc":
logFilteringValueOnProperty(theFilter.getValue(), theFilter.getProperty());
addFilterLoincCopyrightLoinc(theBool);
break;
default:
throwInvalidRequestForValueOnProperty(theFilter.getValue(), theFilter.getProperty());
}
} else {
throwInvalidRequestForOpOnProperty(theFilter.getOp(), theFilter.getProperty());
}
}
private void addFilterLoincCopyright3rdParty(BooleanJunction<?> theBool) {
theBool.must(getRegexQueryForFilterLoincCopyright());
}
private void addFilterLoincCopyrightLoinc(BooleanJunction<?> theBool) {
theBool.must(getRegexQueryForFilterLoincCopyright()).not();
}
private RegexpQuery getRegexQueryForFilterLoincCopyright() {
Term term = new Term(TermConceptPropertyFieldBridge.CONCEPT_FIELD_PROPERTY_PREFIX + "EXTERNAL_COPYRIGHT_NOTICE", ".*");
return new RegexpQuery(term);
}
private void logFilteringValueOnProperty(String theValue, String theProperty) {
ourLog.debug(" * Filtering with value={} on property {}", theValue, theProperty);
}
private void throwInvalidRequestForOpOnProperty(ValueSet.FilterOperator theOp, String theProperty) {
throw new InvalidRequestException("Don't know how to handle op=" + theOp + " on property " + theProperty);
}
private void throwInvalidRequestForValueOnProperty(String theValue, String theProperty) {
throw new InvalidRequestException("Don't know how to handle value=" + theValue + " on property " + theProperty);
}
private void handleFilterRegex(BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
private void handleFilterRegex(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB, ValueSet.ConceptSetFilterComponent theFilter) {
if (theFilter.getOp() == ValueSet.FilterOperator.REGEX) {
/*
@ -1330,19 +1136,274 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
value = value.substring(1);
}
Term term = new Term(TermConceptPropertyFieldBridge.CONCEPT_FIELD_PROPERTY_PREFIX + theFilter.getProperty(), value);
RegexpQuery query = new RegexpQuery(term);
theBool.must(query);
Term term = new Term(TermConceptPropertyBinder.CONCEPT_FIELD_PROPERTY_PREFIX + theFilter.getProperty(), value);
if (isFullTextSetToUseElastic()) {
String regexpQuery = "{'regexp':{'" + term.field() + "':{'value':'" + term.text() + "'}}}";
ourLog.debug("Build Elasticsearch Regexp Query: {}", regexpQuery);
theB.must(theF.extension(ElasticsearchExtension.get()).fromJson(regexpQuery));
} else {
RegexpQuery query = new RegexpQuery(term);
theB.must(theF.extension(LuceneExtension.get()).fromLuceneQuery(query));
}
} else {
String value = theFilter.getValue();
Term term = new Term(TermConceptPropertyFieldBridge.CONCEPT_FIELD_PROPERTY_PREFIX + theFilter.getProperty(), value);
theBool.must(new TermsQuery(term));
Term term = new Term(TermConceptPropertyBinder.CONCEPT_FIELD_PROPERTY_PREFIX + theFilter.getProperty(), value);
theB.must(theF.match().field(term.field()).matching(term.text()));
}
}
private void handleFilterLoincCopyright(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB, ValueSet.ConceptSetFilterComponent theFilter) {
if (theFilter.getOp() == ValueSet.FilterOperator.EQUAL) {
String copyrightFilterValue = defaultString(theFilter.getValue()).toLowerCase();
switch (copyrightFilterValue) {
case "3rdparty":
logFilteringValueOnProperty(theFilter.getValue(), theFilter.getProperty());
addFilterLoincCopyright3rdParty(theF, theB);
break;
case "loinc":
logFilteringValueOnProperty(theFilter.getValue(), theFilter.getProperty());
addFilterLoincCopyrightLoinc(theF, theB);
break;
default:
throwInvalidRequestForValueOnProperty(theFilter.getValue(), theFilter.getProperty());
}
} else {
throwInvalidRequestForOpOnProperty(theFilter.getOp(), theFilter.getProperty());
}
}
private void addFilterLoincCopyrightLoinc(SearchPredicateFactory thePredicateFactory, BooleanPredicateClausesStep<?> theBooleanClause) {
theBooleanClause.mustNot(thePredicateFactory.exists().field(TermConceptPropertyBinder.CONCEPT_FIELD_PROPERTY_PREFIX + "EXTERNAL_COPYRIGHT_NOTICE"));
}
private void addFilterLoincCopyright3rdParty(SearchPredicateFactory thePredicateFactory, BooleanPredicateClausesStep<?> theBooleanClause) {
//TODO GGG HS These used to be Term term = new Term(TermConceptPropertyBinder.CONCEPT_FIELD_PROPERTY_PREFIX + "EXTERNAL_COPYRIGHT_NOTICE", ".*");, which was lucene-specific.
//TODO GGG HS ask diederik if this is equivalent.
//This old .* regex is the same as an existence check on a field, which I've implemented here.
theBooleanClause.must(thePredicateFactory.exists().field(TermConceptPropertyBinder.CONCEPT_FIELD_PROPERTY_PREFIX + "EXTERNAL_COPYRIGHT_NOTICE"));
}
private void handleFilterLoincAncestor2(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
case EQUAL:
addLoincFilterAncestorEqual(theSystem, f, b, theFilter);
break;
case IN:
addLoincFilterAncestorIn(theSystem, f, b, theFilter);
break;
default:
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
}
}
private void addLoincFilterAncestorEqual(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
addLoincFilterAncestorEqual(theSystem, f, b, theFilter.getProperty(), theFilter.getValue());
}
private void addLoincFilterAncestorEqual(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, String theProperty, String theValue) {
List<Term> terms = getAncestorTerms(theSystem, theProperty, theValue);
b.must(f.bool(innerB -> terms.forEach(term -> innerB.should(f.match().field(term.field()).matching(term.text())))));
}
private void addLoincFilterAncestorIn(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(",");
List<Term> terms = new ArrayList<>();
for (String value : values) {
terms.addAll(getAncestorTerms(theSystem, theFilter.getProperty(), value));
}
b.must(f.bool(innerB -> terms.forEach(term -> innerB.should(f.match().field(term.field()).matching(term.text())))));
}
private void handleFilterLoincParentChild(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
case EQUAL:
addLoincFilterParentChildEqual(f, b, theFilter.getProperty(), theFilter.getValue());
break;
case IN:
addLoincFilterParentChildIn(f, b, theFilter);
break;
default:
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
}
}
private void addLoincFilterParentChildIn(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(",");
List<Term> terms = new ArrayList<>();
for (String value : values) {
logFilteringValueOnProperty(value, theFilter.getProperty());
terms.add(getPropertyTerm(theFilter.getProperty(), value));
}
//TODO GGG HS: Not sure if this is the right equivalent...seems to be no equivalent to `TermsQuery` in HS6.
//Far as I'm aware, this is a single element of a MUST portion of a bool, which itself should contain a list of OR'ed options, e.g.
// shape == round && color == (green || blue)
b.must(f.bool(innerB -> terms.forEach(term -> innerB.should(f.match().field(term.field()).matching(term.text())))));
}
private void addLoincFilterParentChildEqual(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, String theProperty, String theValue) {
logFilteringValueOnProperty(theValue, theProperty);
//TODO GGG HS: Not sure if this is the right equivalent...seems to be no equivalent to `TermsQuery` in HS6.
//b.must(new TermsQuery(getPropertyTerm(theProperty, theValue)));
//According to the DSL migration reference (https://docs.jboss.org/hibernate/search/6.0/migration/html_single/#queries-reference),
//Since this property is handled with a specific analyzer, I'm not sure a terms match here is actually correct. The analyzer is literally just a whitespace tokenizer here.
b.must(f.match().field(TermConceptPropertyBinder.CONCEPT_FIELD_PROPERTY_PREFIX + theProperty).matching(theValue));
}
private void handleFilterConceptAndCode(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
TermConcept code = findCode(theSystem, theFilter.getValue())
.orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theFilter.getValue()));
if (theFilter.getOp() == ValueSet.FilterOperator.ISA) {
ourLog.debug(" * Filtering on codes with a parent of {}/{}/{}", code.getId(), code.getCode(), code.getDisplay());
b.must(f.match().field("myParentPids").matching("" + code.getId()));
} else {
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
}
}
private void isCodeSystemLoincOrThrowInvalidRequestException(String theSystemIdentifier, String theProperty) {
String systemUrl = getUrlFromIdentifier(theSystemIdentifier);
if (!isCodeSystemLoinc(systemUrl)) {
throw new InvalidRequestException("Invalid filter, property " + theProperty + " is LOINC-specific and cannot be used with system: " + systemUrl);
}
}
private boolean isCodeSystemLoinc(String theSystem) {
return ITermLoaderSvc.LOINC_URI.equals(theSystem);
}
private void handleFilterDisplay(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
if (theFilter.getProperty().equals("display:exact") && theFilter.getOp() == ValueSet.FilterOperator.EQUAL) {
addDisplayFilterExact(f, b, theFilter);
} else if (theFilter.getProperty().equals("display") && theFilter.getOp() == ValueSet.FilterOperator.EQUAL) {
if (theFilter.getValue().trim().contains(" ")) {
addDisplayFilterExact(f, b, theFilter);
} else {
addDisplayFilterInexact(f, b, theFilter);
}
}
}
private void addDisplayFilterExact(SearchPredicateFactory f, BooleanPredicateClausesStep<?> bool, ValueSet.ConceptSetFilterComponent nextFilter) {
bool.must(f.phrase().field("myDisplay").matching(nextFilter.getValue()));
}
private void addDisplayFilterInexact(SearchPredicateFactory f, BooleanPredicateClausesStep<?> bool, ValueSet.ConceptSetFilterComponent nextFilter) {
bool.must(f.phrase()
.field("myDisplay").boost(4.0f)
.field("myDisplayWordEdgeNGram").boost(1.0f)
.field("myDisplayEdgeNGram").boost(1.0f)
.matching(nextFilter.getValue().toLowerCase())
.slop(2)
);
}
private Term getPropertyTerm(String theProperty, String theValue) {
return new Term(TermConceptPropertyBinder.CONCEPT_FIELD_PROPERTY_PREFIX + theProperty, theValue);
}
private List<Term> getAncestorTerms(String theSystem, String theProperty, String theValue) {
List<Term> retVal = new ArrayList<>();
TermConcept code = findCode(theSystem, theValue)
.orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theValue));
retVal.add(new Term("myParentPids", "" + code.getId()));
logFilteringValueOnProperty(theValue, theProperty);
return retVal;
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincDescendant(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
case EQUAL:
addLoincFilterDescendantEqual(theSystem, f, b, theFilter);
break;
case IN:
addLoincFilterDescendantIn(theSystem, f,b , theFilter);
break;
default:
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
}
}
private void addLoincFilterDescendantEqual(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
addLoincFilterDescendantEqual(theSystem, f, b, theFilter.getProperty(), theFilter.getValue());
}
private void addLoincFilterDescendantIn(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(",");
List<Term> terms = new ArrayList<>();
for (String value : values) {
terms.addAll(getDescendantTerms(theSystem, theFilter.getProperty(), value));
}
searchByParentPids(f, b, terms);
}
private void addLoincFilterDescendantEqual(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, String theProperty, String theValue) {
List<Term> terms = getDescendantTerms(theSystem, theProperty, theValue);
searchByParentPids(f, b, terms);
}
private void searchByParentPids(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, List<Term> theTerms) {
List<Long> parentPids = convertTermsToParentPids(theTerms);
b.must(f.bool(innerB -> {
parentPids.forEach(pid -> innerB.should(f.match().field(theTerms.get(0).field()).matching(pid)));
}));
}
private List<Long> convertTermsToParentPids(List<Term> theTerms) {
return theTerms.stream().map(Term::text).map(Long::valueOf).collect(Collectors.toList());
}
private List<Term> getDescendantTerms(String theSystem, String theProperty, String theValue) {
List<Term> retVal = new ArrayList<>();
TermConcept code = findCode(theSystem, theValue)
.orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theValue));
String[] parentPids = code.getParentPidsAsString().split(" ");
for (String parentPid : parentPids) {
if (!StringUtils.equals(parentPid, "NONE")) {
retVal.add(new Term("myId", parentPid));
}
}
logFilteringValueOnProperty(theValue, theProperty);
return retVal;
}
private void logFilteringValueOnProperty(String theValue, String theProperty) {
ourLog.debug(" * Filtering with value={} on property {}", theValue, theProperty);
}
private void throwInvalidRequestForOpOnProperty(ValueSet.FilterOperator theOp, String theProperty) {
throw new InvalidRequestException("Don't know how to handle op=" + theOp + " on property " + theProperty);
}
private void throwInvalidRequestForValueOnProperty(String theValue, String theProperty) {
throw new InvalidRequestException("Don't know how to handle value=" + theValue + " on property " + theProperty);
}
private void expandWithoutHibernateSearch(IValueSetConceptAccumulator theValueSetCodeAccumulator, TermCodeSystemVersion theVersion, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theInclude, String theSystem, boolean theAdd) {
ourLog.trace("Hibernate search is not enabled");
@ -1665,7 +1726,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
public void scheduleJob() {
// TODO KHS what does this mean?
// Register scheduled job to pre-expand ValueSets
// In the future it would be great to make this a cluster-aware task somehow
ScheduledJobDefinition vsJobDefinition = new ScheduledJobDefinition();
@ -2054,12 +2114,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
TermConcept codeB = findCode(codeBSystemIdentifier, conceptB.getCode())
.orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptB));
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
SearchSession searchSession = Search.session(myEntityManager);
ConceptSubsumptionOutcome subsumes;
subsumes = testForSubsumption(em, codeA, codeB, ConceptSubsumptionOutcome.SUBSUMES);
subsumes = testForSubsumption(searchSession, codeA, codeB, ConceptSubsumptionOutcome.SUBSUMES);
if (subsumes == null) {
subsumes = testForSubsumption(em, codeB, codeA, ConceptSubsumptionOutcome.SUBSUMEDBY);
subsumes = testForSubsumption(searchSession, codeB, codeA, ConceptSubsumptionOutcome.SUBSUMEDBY);
}
if (subsumes == null) {
subsumes = ConceptSubsumptionOutcome.NOTSUBSUMED;
@ -2116,20 +2176,21 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
@Nullable
private ConceptSubsumptionOutcome testForSubsumption(FullTextEntityManager theEntityManager, TermConcept theLeft, TermConcept theRight, ConceptSubsumptionOutcome theOutput) {
QueryBuilder qb = theEntityManager.getSearchFactory().buildQueryBuilder().forEntity(TermConcept.class).get();
BooleanJunction<?> bool = qb.bool();
bool.must(qb.keyword().onField("myId").matching(Long.toString(theRight.getId())).createQuery());
bool.must(qb.keyword().onField("myParentPids").matching(Long.toString(theLeft.getId())).createQuery());
Query luceneQuery = bool.createQuery();
FullTextQuery jpaQuery = theEntityManager.createFullTextQuery(luceneQuery, TermConcept.class);
jpaQuery.setMaxResults(1);
if (jpaQuery.getResultList().size() > 0) {
private ConceptSubsumptionOutcome testForSubsumption(SearchSession theSearchSession, TermConcept theLeft, TermConcept theRight, ConceptSubsumptionOutcome theOutput) {
List<TermConcept> fetch = theSearchSession.search(TermConcept.class)
.where(f -> f.bool()
.must(f.match().field("myId").matching(theRight.getId()))
.must(f.match().field("myParentPids").matching(Long.toString(theLeft.getId())))
).fetchHits(1);
if (fetch.size() > 0) {
return theOutput;
} else {
return null;
}
return null;
}
private ArrayList<FhirVersionIndependentConcept> toVersionIndependentConcepts(String theSystem, Set<TermConcept> codes) {
ArrayList<FhirVersionIndependentConcept> retVal = new ArrayList<>(codes.size());
for (TermConcept next : codes) {

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.IHapiJpaRepository;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
@ -52,7 +51,6 @@ import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
@ -62,19 +60,17 @@ import org.hl7.fhir.r4.model.ValueSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@ -83,13 +79,16 @@ import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
@ -183,57 +182,116 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
if (cs == null) {
throw new InvalidRequestException("Unknown code system: " + theSystem);
}
IIdType target = cs.getResource().getIdDt();
AtomicInteger removeCounter = new AtomicInteger(0);
for (TermConcept nextSuppliedConcept : theValue.getRootConcepts()) {
Optional<TermConcept> conceptOpt = myTerminologySvc.findCode(theSystem, nextSuppliedConcept.getCode());
if (conceptOpt.isPresent()) {
TermConcept concept = conceptOpt.get();
deleteConceptChildrenAndConcept(concept, removeCounter);
}
//We need to delete all termconcepts, and their children. This stream flattens the TermConcepts and their
//children into a single set of TermConcept objects retrieved from the DB. Note that we have to do this because
//deleteById() in JPA doesnt appear to actually commit or flush a transaction until way later, and we end up
//iterating multiple times over the same elements, which screws up our counter.
//Grab the actual entities
List<TermConcept> collect = theValue.getRootConcepts().stream()
.map(val -> myTerminologySvc.findCode(theSystem, val.getCode()))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
//Iterate over the actual entities and fill out their children
Set<TermConcept> allFoundTermConcepts = collect
.stream()
.flatMap(concept -> flattenChildren(concept).stream())
.map(suppliedTermConcept -> myTerminologySvc.findCode(theSystem, suppliedTermConcept.getCode()))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());
//Delete everything about these codes.
for (TermConcept code : allFoundTermConcepts) {
deleteEverythingRelatedToConcept(code, removeCounter);
}
IIdType target = cs.getResource().getIdDt();
return new UploadStatistics(removeCounter.get(), target);
}
private void deleteEverythingRelatedToConcept(TermConcept theConcept, AtomicInteger theRemoveCounter) {
for (TermConceptParentChildLink nextParent : theConcept.getParents()) {
nextParent.getParent().getChildren().remove(nextParent);
myConceptParentChildLinkDao.deleteById(nextParent.getId());
}
for (TermConceptParentChildLink nextChild : theConcept.getChildren()) {
nextChild.getChild().getParents().remove(nextChild);
myConceptParentChildLinkDao.deleteById(nextChild.getId());
}
for (TermConceptDesignation next : theConcept.getDesignations()) {
myConceptDesignationDao.deleteById(next.getPid());
}
theConcept.getDesignations().clear();
for (TermConceptProperty next : theConcept.getProperties()) {
myConceptPropertyDao.deleteById(next.getPid());
}
theConcept.getProperties().clear();
ourLog.info("Deleting concept {} - Code {}", theConcept.getId(), theConcept.getCode());
myConceptDao.deleteById(theConcept.getId());
// myEntityManager.remove(theConcept);
theRemoveCounter.incrementAndGet();
}
private List<TermConcept> flattenChildren(TermConcept theTermConcept) {
if (theTermConcept.getChildren().isEmpty()) {
return Arrays.asList(theTermConcept);
}
//Recursively flatten children
List<TermConcept> childTermConcepts = theTermConcept.getChildren().stream()
.map(TermConceptParentChildLink::getChild)
.flatMap(childConcept -> flattenChildren(childConcept).stream())
.collect(Collectors.toList());
//Add itself before its list of children
childTermConcepts.add(0, theTermConcept);
return childTermConcepts;
}
@Override
@Transactional(propagation = Propagation.NEVER)
@Transactional
public void deleteCodeSystem(TermCodeSystem theCodeSystem) {
assert TransactionSynchronizationManager.isActualTransactionActive();
ourLog.info(" * Deleting code system {}", theCodeSystem.getPid());
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txTemplate.executeWithoutResult(t -> {
myEntityManager.flush();
TermCodeSystem cs = myCodeSystemDao.findById(theCodeSystem.getPid()).orElseThrow(IllegalStateException::new);
cs.setCurrentVersion(null);
myCodeSystemDao.save(cs);
myCodeSystemDao.flush();
});
myEntityManager.flush();
TermCodeSystem cs = myCodeSystemDao.findById(theCodeSystem.getPid()).orElseThrow(IllegalStateException::new);
cs.setCurrentVersion(null);
myCodeSystemDao.save(cs);
myCodeSystemDao.flush();
List<Long> codeSystemVersionPids = txTemplate.execute(t -> {
List<TermCodeSystemVersion> codeSystemVersions = myCodeSystemVersionDao.findByCodeSystemPid(theCodeSystem.getPid());
return codeSystemVersions
.stream()
.map(v -> v.getPid())
.collect(Collectors.toList());
});
List<TermCodeSystemVersion> codeSystemVersions = myCodeSystemVersionDao.findByCodeSystemPid(theCodeSystem.getPid());
List<Long> codeSystemVersionPids = codeSystemVersions
.stream()
.map(TermCodeSystemVersion::getPid)
.collect(Collectors.toList());
for (Long next : codeSystemVersionPids) {
deleteCodeSystemVersion(next);
}
txTemplate.executeWithoutResult(t -> {
myCodeSystemVersionDao.deleteForCodeSystem(theCodeSystem);
myCodeSystemDao.delete(theCodeSystem);
myEntityManager.flush();
});
myCodeSystemVersionDao.deleteForCodeSystem(theCodeSystem);
myCodeSystemDao.delete(theCodeSystem);
myEntityManager.flush();
}
@Override
@Transactional(propagation = Propagation.NEVER)
public void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
assert !TransactionSynchronizationManager.isActualTransactionActive();
// Delete TermCodeSystemVersion
ourLog.info(" * Deleting TermCodeSystemVersion {}", theCodeSystemVersion.getCodeSystemVersionId());
deleteCodeSystemVersion(theCodeSystemVersion.getPid());
@ -322,11 +380,15 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
@Transactional
public IIdType storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequest, List<ValueSet> theValueSets, List<ConceptMap> theConceptMaps) {
assert TransactionSynchronizationManager.isActualTransactionActive();
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
// Note that this creates the TermCodeSystem and TermCodeSystemVersion entities if needed
IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource);
ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(RequestPartitionId.allPartitions(), csId.getResourceType(), csId.getIdPart());
ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong());
@ -343,35 +405,52 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void storeNewCodeSystemVersion(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable) {
@Transactional
public void storeNewCodeSystemVersion(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theCodeSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable) {
assert TransactionSynchronizationManager.isActualTransactionActive();
ourLog.debug("Storing code system");
ValidateUtil.isTrueOrThrowInvalidRequest(theCodeSystemVersion.getResource() != null, "No resource supplied");
TermCodeSystemVersion codeSystemToStore = theCodeSystemVersion;
ValidateUtil.isTrueOrThrowInvalidRequest(codeSystemToStore.getResource() != null, "No resource supplied");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied");
// Grab the existing version so we can delete it
TermCodeSystem codeSystem = getOrCreateDistinctTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theCodeSystemVersionId, theCodeSystemResourceTable);
List<TermCodeSystemVersion> existing = myCodeSystemVersionDao.findByCodeSystemResourcePid(theCodeSystemResourcePid.getIdAsLong());
/*
* Delete version being replaced.
*/
for (TermCodeSystemVersion next : existing) {
ourLog.info("Deleting old code system version {}", next.getPid());
Long codeSystemVersionPid = next.getPid();
deleteCodeSystemVersion(codeSystemVersionPid);
if (Objects.equals(next.getCodeSystemVersionId(), theCodeSystemVersionId) && myConceptDao.countByCodeSystemVersion(next.getPid()) == 0) {
/*
* If we already have a CodeSystemVersion that matches the version we're storing, we
* can reuse it.
*/
next.setCodeSystemDisplayName(theSystemName);
codeSystemToStore = next;
} else {
/*
* If we already have a TermCodeSystemVersion that corresponds to the FHIR Resource ID we're
* adding a version to, we will mark it for deletion. For any one resource there can only
* be one TermCodeSystemVersion entity in the DB. Multiple versions of a codesystem uses
* multiple CodeSystem resources with CodeSystem.version set differently (as opposed to
* multiple versions of the same CodeSystem, where CodeSystem.meta.versionId is different)
*/
next.setCodeSystemVersionId("DELETED_" + UUID.randomUUID().toString());
myCodeSystemVersionDao.saveAndFlush(next);
myDeferredStorageSvc.deleteCodeSystemVersion(next);
}
}
/*
* Do the upload
*/
TermCodeSystem codeSystem = getOrCreateDistinctTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theSystemVersionId, theCodeSystemResourceTable);
theCodeSystemVersion.setCodeSystem(codeSystem);
theCodeSystemVersion.setCodeSystemDisplayName(theSystemName);
theCodeSystemVersion.setCodeSystemVersionId(theSystemVersionId);
codeSystemToStore.setCodeSystem(codeSystem);
codeSystemToStore.setCodeSystemDisplayName(theSystemName);
codeSystemToStore.setCodeSystemVersionId(theCodeSystemVersionId);
ourLog.debug("Validating all codes in CodeSystem for storage (this can take some time for large sets)");
@ -379,40 +458,42 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
ArrayList<String> conceptsStack = new ArrayList<>();
IdentityHashMap<TermConcept, Object> allConcepts = new IdentityHashMap<>();
int totalCodeCount = 0;
for (TermConcept next : theCodeSystemVersion.getConcepts()) {
totalCodeCount += validateConceptForStorage(next, theCodeSystemVersion, conceptsStack, allConcepts);
Collection<TermConcept> conceptsToSave = theCodeSystemVersion.getConcepts();
for (TermConcept next : conceptsToSave) {
totalCodeCount += validateConceptForStorage(next, codeSystemToStore, conceptsStack, allConcepts);
}
ourLog.debug("Saving version containing {} concepts", totalCodeCount);
TermCodeSystemVersion codeSystemVersion = myCodeSystemVersionDao.saveAndFlush(theCodeSystemVersion);
if (codeSystemToStore.getPid() == null) {
codeSystemToStore = myCodeSystemVersionDao.saveAndFlush(codeSystemToStore);
}
ourLog.debug("Saving code system");
codeSystem.setCurrentVersion(theCodeSystemVersion);
codeSystem = myCodeSystemDao.saveAndFlush(codeSystem);
codeSystem.setCurrentVersion(codeSystemToStore);
if (codeSystem.getPid() == null) {
codeSystem = myCodeSystemDao.saveAndFlush(codeSystem);
}
ourLog.debug("Setting CodeSystemVersion[{}] on {} concepts...", codeSystem.getPid(), totalCodeCount);
for (TermConcept next : theCodeSystemVersion.getConcepts()) {
populateVersion(next, codeSystemVersion);
for (TermConcept next : conceptsToSave) {
populateVersion(next, codeSystemToStore);
}
ourLog.debug("Saving {} concepts...", totalCodeCount);
IdentityHashMap<TermConcept, Object> conceptsStack2 = new IdentityHashMap<>();
for (TermConcept next : theCodeSystemVersion.getConcepts()) {
persistChildren(next, codeSystemVersion, conceptsStack2, totalCodeCount);
for (TermConcept next : conceptsToSave) {
persistChildren(next, codeSystemToStore, conceptsStack2, totalCodeCount);
}
ourLog.debug("Done saving concepts, flushing to database");
myConceptDao.flush();
myConceptParentChildLinkDao.flush();
if (myDeferredStorageSvc.isStorageQueueEmpty() == false) {
ourLog.info("Note that some concept saving has been deferred");
}
}
private TermCodeSystemVersion getExistingTermCodeSystemVersion(Long theCodeSystemVersionPid, String theCodeSystemVersion) {
@ -427,60 +508,24 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}
private void deleteCodeSystemVersion(final Long theCodeSystemVersionPid) {
ourLog.info(" * Deleting code system version {}", theCodeSystemVersionPid);
assert TransactionSynchronizationManager.isActualTransactionActive();
ourLog.info(" * Marking code system version {} for deletion", theCodeSystemVersionPid);
PageRequest page1000 = PageRequest.of(0, 1000);
// Parent/Child links
{
String descriptor = "parent/child links";
Supplier<Slice<Long>> loader = () -> myConceptParentChildLinkDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptParentChildLinkDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptParentChildLinkDao);
}
// Properties
{
String descriptor = "concept properties";
Supplier<Slice<Long>> loader = () -> myConceptPropertyDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptPropertyDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptPropertyDao);
}
// Designations
{
String descriptor = "concept designations";
Supplier<Slice<Long>> loader = () -> myConceptDesignationDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptDesignationDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDesignationDao);
}
// Concepts
{
String descriptor = "concepts";
// For some reason, concepts are much slower to delete, so use a smaller batch size
PageRequest page100 = PageRequest.of(0, 100);
Supplier<Slice<Long>> loader = () -> myConceptDao.findIdsByCodeSystemVersion(page100, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDao);
}
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.executeWithoutResult(tx -> {
Optional<TermCodeSystem> codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid);
if (codeSystemOpt.isPresent()) {
TermCodeSystem codeSystem = codeSystemOpt.get();
Optional<TermCodeSystem> codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid);
if (codeSystemOpt.isPresent()) {
TermCodeSystem codeSystem = codeSystemOpt.get();
if (codeSystem.getCurrentVersion() != null && codeSystem.getCurrentVersion().getPid().equals(theCodeSystemVersionPid)) {
ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid());
codeSystem.setCurrentVersion(null);
myCodeSystemDao.save(codeSystem);
myCodeSystemDao.flush();
}
}
ourLog.info(" * Deleting code system version");
myCodeSystemVersionDao.delete(theCodeSystemVersionPid);
myCodeSystemVersionDao.flush();
});
TermCodeSystemVersion codeSystemVersion = myCodeSystemVersionDao.findById(theCodeSystemVersionPid).orElseThrow(() -> new IllegalStateException());
codeSystemVersion.setCodeSystemVersionId("DELETED_" + UUID.randomUUID().toString());
myCodeSystemVersionDao.save(codeSystemVersion);
myDeferredStorageSvc.deleteCodeSystemVersion(codeSystemVersion);
}
private void validateDstu3OrNewer() {
@ -635,13 +680,12 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}
private void populateVersion(TermConcept theNext, TermCodeSystemVersion theCodeSystemVersion) {
if (theNext.getCodeSystemVersion() != null) {
return;
}
theNext.setCodeSystemVersion(theCodeSystemVersion);
for (TermConceptParentChildLink next : theNext.getChildren()) {
populateVersion(next.getChild(), theCodeSystemVersion);
}
theNext.getProperties().forEach(t->t.setCodeSystemVersion(theCodeSystemVersion));
theNext.getDesignations().forEach(t->t.setCodeSystemVersion(theCodeSystemVersion));
}
private void saveConceptLink(TermConceptParentChildLink next) {
@ -702,7 +746,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
// Check if a TermCodeSystemVersion entity already exists for this TermCodeSystem and version.
codeSystemVersionEntity = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(theCodeSystem.getPid(), theSystemVersionId);
if (codeSystemVersionEntity != null) {
msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrlAndVersion", theSystemUri, theSystemVersionId, codeSystemVersionEntity.getResource().getIdDt().toUnqualifiedVersionless().getValue());
msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrlAndVersion", theSystemUri, theSystemVersionId, codeSystemVersionEntity.getResource().getIdDt().toUnqualifiedVersionless().getValue());
}
}
// Throw exception if the TermCodeSystemVersion is being duplicated.
@ -719,58 +763,13 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
theCodeSystemVersion.setCodeSystemVersionId(theCodeSystemResource.getVersion());
}
private void deleteConceptChildrenAndConcept(TermConcept theConcept, AtomicInteger theRemoveCounter) {
for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) {
deleteConceptChildrenAndConcept(nextChildLink.getChild(), theRemoveCounter);
}
myConceptParentChildLinkDao.deleteByConceptPid(theConcept.getId());
myConceptDesignationDao.deleteAll(theConcept.getDesignations());
myConceptPropertyDao.deleteAll(theConcept.getProperties());
ourLog.info("Deleting concept {} - Code {}", theConcept.getId(), theConcept.getCode());
myConceptDao.deleteByPid(theConcept.getId());
theRemoveCounter.incrementAndGet();
}
@SuppressWarnings("ConstantConditions")
private <T> void doDelete(String theDescriptor, Supplier<Slice<Long>> theLoader, Supplier<Integer> theCounter, IHapiJpaRepository<T> theDao) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
int count;
ourLog.info(" * Deleting {}", theDescriptor);
int totalCount = txTemplate.execute(t -> theCounter.get());
StopWatch sw = new StopWatch();
count = 0;
while (true) {
Slice<Long> link = txTemplate.execute(t -> theLoader.get());
if (!link.hasContent()) {
break;
}
txTemplate.execute(t -> {
link.forEach(id -> theDao.deleteByPid(id));
theDao.flush();
return null;
});
count += link.getNumberOfElements();
ourLog.info(" * {} {} deleted ({}/{}) remaining - {}/sec - ETA: {}", count, theDescriptor, count, totalCount, sw.formatThroughput(count, TimeUnit.SECONDS), sw.getEstimatedTimeRemaining(count, totalCount));
}
}
private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, ArrayList<String> theConceptsStack,
private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystemVersion, ArrayList<String> theConceptsStack,
IdentityHashMap<TermConcept, Object> theAllConcepts) {
ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() != null, "CodeSystemVersion is null");
ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() == theCodeSystem, "CodeSystems are not equal");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "CodeSystem contains a code with no code value");
theConcept.setCodeSystemVersion(theCodeSystemVersion);
if (theConceptsStack.contains(theConcept.getCode())) {
throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode());
}
@ -785,8 +784,8 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}
for (TermConceptParentChildLink next : theConcept.getChildren()) {
next.setCodeSystem(theCodeSystem);
retVal += validateConceptForStorage(next.getChild(), theCodeSystem, theConceptsStack, theAllConcepts);
next.setCodeSystem(theCodeSystemVersion);
retVal += validateConceptForStorage(next.getChild(), theCodeSystemVersion, theConceptsStack, theAllConcepts);
}
theConceptsStack.remove(theConceptsStack.size() - 1);

View File

@ -23,7 +23,9 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDesignationDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptPropertyDao;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
@ -44,31 +46,27 @@ import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
private static final Logger ourLog = LoggerFactory.getLogger(TermDeferredStorageSvcImpl.class);
@Autowired
protected ITermConceptDao myConceptDao;
@Autowired
protected ITermCodeSystemDao myCodeSystemDao;
@Autowired
protected ITermCodeSystemVersionDao myCodeSystemVersionDao;
@Autowired
protected PlatformTransactionManager myTransactionMgr;
private boolean myProcessDeferred = true;
final private List<TermCodeSystem> myDeferredCodeSystemsDeletions = Collections.synchronizedList(new ArrayList<>());
final private List<TermCodeSystemVersion> myDeferredCodeSystemVersionsDeletions = Collections.synchronizedList(new ArrayList<>());
final private List<TermConcept> myDeferredConcepts = Collections.synchronizedList(new ArrayList<>());
@ -76,6 +74,19 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
final private List<ConceptMap> myDeferredConceptMaps = Collections.synchronizedList(new ArrayList<>());
final private List<TermConceptParentChildLink> myConceptLinksToSaveLater = Collections.synchronizedList(new ArrayList<>());
@Autowired
protected ITermConceptDao myConceptDao;
@Autowired
protected ITermCodeSystemDao myCodeSystemDao;
@Autowired
protected ITermCodeSystemVersionDao myCodeSystemVersionDao;
@Autowired
protected PlatformTransactionManager myTransactionMgr;
@Autowired
protected ITermConceptPropertyDao myConceptPropertyDao;
@Autowired
protected ITermConceptDesignationDao myConceptDesignationDao;
private boolean myProcessDeferred = true;
@Autowired
private ITermConceptParentChildLinkDao myConceptParentChildLinkDao;
@Autowired
private ISchedulerService mySchedulerService;
@ -120,7 +131,7 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
@Transactional
public void deleteCodeSystemForResource(ResourceTable theCodeSystemToDelete) {
List<TermCodeSystemVersion> codeSystemVersionsToDelete = myCodeSystemVersionDao.findByCodeSystemResourcePid(theCodeSystemToDelete.getResourceId());
for (TermCodeSystemVersion codeSystemVersionToDelete : codeSystemVersionsToDelete){
for (TermCodeSystemVersion codeSystemVersionToDelete : codeSystemVersionsToDelete) {
if (codeSystemVersionToDelete != null) {
myDeferredCodeSystemVersionsDeletions.add(codeSystemVersionToDelete);
}
@ -131,13 +142,6 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
}
}
@Override
public void saveAllDeferred() {
while (!isStorageQueueEmpty()) {
saveDeferred();
}
}
@Override
public void setProcessDeferred(boolean theProcessDeferred) {
myProcessDeferred = theProcessDeferred;
@ -161,7 +165,7 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
ourLog.debug("Saving {} deferred concepts...", count);
while (codeCount < count && myDeferredConcepts.size() > 0) {
TermConcept next = myDeferredConcepts.remove(0);
if(myCodeSystemVersionDao.findById(next.getCodeSystemVersion().getPid()).isPresent()) {
if (myCodeSystemVersionDao.findById(next.getCodeSystemVersion().getPid()).isPresent()) {
try {
codeCount += myCodeSystemStorageSvc.saveConcept(next);
} catch (Exception theE) {
@ -232,6 +236,25 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
myDeferredCodeSystemVersionsDeletions.clear();
}
private void runInTransaction(Runnable theRunnable) {
assert !TransactionSynchronizationManager.isActualTransactionActive();
new TransactionTemplate(myTransactionMgr).executeWithoutResult(tx -> theRunnable.run());
}
private <T> T runInTransaction(Supplier<T> theRunnable) {
assert !TransactionSynchronizationManager.isActualTransactionActive();
return new TransactionTemplate(myTransactionMgr).execute(tx -> theRunnable.get());
}
@Override
public void saveAllDeferred() {
while (!isStorageQueueEmpty()) {
saveDeferred();
}
}
@Transactional(propagation = Propagation.NEVER)
@Override
public synchronized void saveDeferred() {
@ -249,10 +272,8 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
return;
}
TransactionTemplate tt = new TransactionTemplate(myTransactionMgr);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
if (isDeferredConceptsOrConceptLinksToSaveLater()) {
tt.execute(t -> {
runInTransaction(() -> {
processDeferredConcepts();
return null;
});
@ -261,7 +282,7 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
}
if (isDeferredValueSets()) {
tt.execute(t -> {
runInTransaction(() -> {
processDeferredValueSets();
return null;
});
@ -270,7 +291,7 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
}
if (isDeferredConceptMaps()) {
tt.execute(t -> {
runInTransaction(() -> {
processDeferredConceptMaps();
return null;
});
@ -278,25 +299,116 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
continue;
}
if (isDeferredCodeSystemVersionDeletions()) {
processDeferredCodeSystemVersionDeletions();
}
if (isDeferredCodeSystemDeletions()) {
processDeferredCodeSystemDeletions();
}
}
}
private boolean isDeferredCodeSystemVersionDeletions() {
return !myDeferredCodeSystemVersionsDeletions.isEmpty();
}
private void processDeferredCodeSystemDeletions() {
for (TermCodeSystemVersion next : myDeferredCodeSystemVersionsDeletions) {
myCodeSystemStorageSvc.deleteCodeSystemVersion(next);
}
myDeferredCodeSystemVersionsDeletions.clear();
for (TermCodeSystem next : myDeferredCodeSystemsDeletions) {
myCodeSystemStorageSvc.deleteCodeSystem(next);
}
myDeferredCodeSystemsDeletions.clear();
}
private void processDeferredCodeSystemVersionDeletions() {
for (TermCodeSystemVersion next : myDeferredCodeSystemVersionsDeletions) {
processDeferredCodeSystemVersionDeletions(next.getPid());
}
myDeferredCodeSystemVersionsDeletions.clear();
}
private void processDeferredCodeSystemVersionDeletions(long theCodeSystemVersionPid) {
assert !TransactionSynchronizationManager.isActualTransactionActive();
ourLog.info(" * Deleting CodeSystemVersion[id={}]", theCodeSystemVersionPid);
PageRequest page1000 = PageRequest.of(0, 1000);
// Parent/Child links
{
String descriptor = "parent/child links";
Supplier<Slice<Long>> loader = () -> myConceptParentChildLinkDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptParentChildLinkDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptParentChildLinkDao);
}
// Properties
{
String descriptor = "concept properties";
Supplier<Slice<Long>> loader = () -> myConceptPropertyDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptPropertyDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptPropertyDao);
}
// Designations
{
String descriptor = "concept designations";
Supplier<Slice<Long>> loader = () -> myConceptDesignationDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptDesignationDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDesignationDao);
}
// Concepts
{
String descriptor = "concepts";
// For some reason, concepts are much slower to delete, so use a smaller batch size
PageRequest page100 = PageRequest.of(0, 100);
Supplier<Slice<Long>> loader = () -> myConceptDao.findIdsByCodeSystemVersion(page100, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDao);
}
runInTransaction(() -> {
Optional<TermCodeSystem> codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid);
if (codeSystemOpt.isPresent()) {
TermCodeSystem codeSystem = codeSystemOpt.get();
ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid());
codeSystem.setCurrentVersion(null);
myCodeSystemDao.save(codeSystem);
}
ourLog.info(" * Deleting code system version");
Optional<TermCodeSystemVersion> csv = myCodeSystemVersionDao.findById(theCodeSystemVersionPid);
if (csv.isPresent()) {
myCodeSystemVersionDao.delete(csv.get());
}
});
}
private <T> void doDelete(String theDescriptor, Supplier<Slice<Long>> theLoader, Supplier<Integer> theCounter, JpaRepository<T, Long> theDao) {
assert !TransactionSynchronizationManager.isActualTransactionActive();
int count;
ourLog.info(" * Deleting {}", theDescriptor);
int totalCount = runInTransaction(theCounter);
StopWatch sw = new StopWatch();
count = 0;
while (true) {
Slice<Long> link = runInTransaction(theLoader);
if (!link.hasContent()) {
break;
}
runInTransaction(() -> link.forEach(theDao::deleteById));
count += link.getNumberOfElements();
ourLog.info(" * {} {} deleted ({}/{}) remaining - {}/sec - ETA: {}", count, theDescriptor, count, totalCount, sw.formatThroughput(count, TimeUnit.SECONDS), sw.getEstimatedTimeRemaining(count, totalCount));
}
}
@Override
public boolean isStorageQueueEmpty() {
boolean retVal = !isProcessDeferredPaused();
@ -354,16 +466,6 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
mySchedulerService.scheduleLocalJob(5000, jobDefinition);
}
public static class Job implements HapiJob {
@Autowired
private ITermDeferredStorageSvc myTerminologySvc;
@Override
public void execute(JobExecutionContext theContext) {
myTerminologySvc.saveDeferred();
}
}
@VisibleForTesting
void setTransactionManagerForUnitTest(PlatformTransactionManager theTxManager) {
myTransactionMgr = theTxManager;
@ -395,5 +497,20 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
ourLog.info("isDeferredCodeSystemDeletions: {}", isDeferredCodeSystemDeletions());
}
@Override
public synchronized void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
myDeferredCodeSystemVersionsDeletions.add(theCodeSystemVersion);
}
public static class Job implements HapiJob {
@Autowired
private ITermDeferredStorageSvc myTerminologySvc;
@Override
public void execute(JobExecutionContext theContext) {
myTerminologySvc.saveDeferred();
}
}
}

View File

@ -66,7 +66,7 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
myContext = theContext;
}
@Nonnull
@Nonnull
@Override
public Integer getCapacityRemaining() {
return (myMaxCapacity - myAddedConcepts) + mySkipCountRemaining;

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.term.api;
*/
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@ -57,10 +58,13 @@ public interface ITermDeferredStorageSvc {
void deleteCodeSystemForResource(ResourceTable theCodeSystemResourceToDelete);
void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion);
/**
* This is mostly here for unit tests - Saves any and all deferred concepts and links
*/
void saveAllDeferred();
void logQueueForUnitTest();
}

View File

@ -157,7 +157,7 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
}
/**
* Returns all INSERT queries executed on the current thread - Index 0 is oldest
* Returns all queries executed on the current thread - Index 0 is oldest
*/
public List<SqlQuery> getAllQueriesForCurrentThread() {
return getQueriesForCurrentThreadStartingWith("");
@ -252,14 +252,25 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
}
/**
* Log all captured INSERT queries
* Log all captured queries
*/
public void logAllQueriesForCurrentThread() {
List<String> queries = getAllQueriesForCurrentThread()
.stream()
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
.collect(Collectors.toList());
ourLog.info("Insert Queries:\n{}", String.join("\n", queries));
ourLog.info("Queries:\n{}", String.join("\n", queries));
}
/**
* Log all captured queries
*/
public void logAllQueries() {
List<String> queries = getCapturedQueries()
.stream()
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
.collect(Collectors.toList());
ourLog.info("Queries:\n{}", String.join("\n", queries));
}
/**

View File

@ -21,17 +21,24 @@ package ca.uhn.fhir.jpa.util;
*/
import org.hibernate.search.spatial.impl.Point;
import org.hibernate.search.engine.spatial.GeoPoint;
import org.hibernate.search.engine.spatial.GeoBoundingBox;
import org.slf4j.Logger;
import static ca.uhn.fhir.jpa.searchparam.extractor.GeopointNormalizer.normalizeLatitude;
import static ca.uhn.fhir.jpa.searchparam.extractor.GeopointNormalizer.normalizeLongitude;
import static org.slf4j.LoggerFactory.getLogger;
public class CoordCalculator {
private static final Logger ourLog = getLogger(CoordCalculator.class);
public static final double MAX_SUPPORTED_DISTANCE_KM = 10000.0; // Slightly less than a quarter of the earth's circumference
private static final double RADIUS_EARTH_KM = 6378.1;
// Source: https://stackoverflow.com/questions/7222382/get-lat-long-given-current-point-distance-and-bearing
static Point findTarget(double theLatitudeDegrees, double theLongitudeDegrees, double theBearingDegrees, double theDistanceKm) {
static GeoPoint findTarget(double theLatitudeDegrees, double theLongitudeDegrees, double theBearingDegrees, double theDistanceKm) {
double latitudeRadians = Math.toRadians(Point.normalizeLatitude(theLatitudeDegrees));
double longitudeRadians = Math.toRadians(Point.normalizeLongitude(theLongitudeDegrees));
double latitudeRadians = Math.toRadians(normalizeLatitude(theLatitudeDegrees));
double longitudeRadians = Math.toRadians(normalizeLongitude(theLongitudeDegrees));
double bearingRadians = Math.toRadians(theBearingDegrees);
double distanceRadians = theDistanceKm / RADIUS_EARTH_KM;
@ -41,18 +48,23 @@ public class CoordCalculator {
double targetLongitude = longitudeRadians + Math.atan2(Math.sin(bearingRadians) * Math.sin(distanceRadians) * Math.cos(latitudeRadians),
Math.cos(distanceRadians)-Math.sin(latitudeRadians) * Math.sin(targetLatitude));
return Point.fromDegrees(Math.toDegrees(targetLatitude), Math.toDegrees(targetLongitude));
double latitude = Math.toDegrees(targetLatitude);
double longitude = Math.toDegrees(targetLongitude);
GeoPoint of = GeoPoint.of(normalizeLatitude(latitude), normalizeLongitude(longitude));
return of;
}
/**
* Find a box around my coordinates such that the closest distance to each edge is the provided distance
* @return
*/
public static SearchBox getBox(double theLatitudeDegrees, double theLongitudeDegrees, Double theDistanceKm) {
public static GeoBoundingBox getBox(double theLatitudeDegrees, double theLongitudeDegrees, Double theDistanceKm) {
double diagonalDistanceKm = theDistanceKm * Math.sqrt(2.0);
Point northEast = CoordCalculator.findTarget(theLatitudeDegrees, theLongitudeDegrees, 45.0, diagonalDistanceKm);
Point southWest = CoordCalculator.findTarget(theLatitudeDegrees, theLongitudeDegrees, 225.0, diagonalDistanceKm);
GeoPoint topLeft = CoordCalculator.findTarget(theLatitudeDegrees, theLongitudeDegrees, 315.0, diagonalDistanceKm);
GeoPoint bottomRight = CoordCalculator.findTarget(theLatitudeDegrees, theLongitudeDegrees, 135.0, diagonalDistanceKm);
return new SearchBox(southWest, northEast);
return GeoBoundingBox.of(topLeft, bottomRight);
}
}

View File

@ -1,45 +0,0 @@
package ca.uhn.fhir.jpa.util;
/*-
* #%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 org.hibernate.search.spatial.impl.Point;
public class SearchBox {
private final Point mySouthWest;
private final Point myNorthEast;
public SearchBox(Point theSouthWest, Point theNorthEast) {
mySouthWest = theSouthWest;
myNorthEast = theNorthEast;
}
public Point getSouthWest() {
return mySouthWest;
}
public Point getNorthEast() {
return myNorthEast;
}
public boolean crossesAntiMeridian() {
return myNorthEast.getLongitude() < mySouthWest.getLongitude();
}
}

View File

@ -182,7 +182,9 @@ public class TestUtil {
OneToOne oneToOne = nextField.getAnnotation(OneToOne.class);
boolean isOtherSideOfOneToManyMapping = oneToMany != null && isNotBlank(oneToMany.mappedBy());
boolean isOtherSideOfOneToOneMapping = oneToOne != null && isNotBlank(oneToOne.mappedBy());
boolean isField = nextField.getAnnotation(org.hibernate.search.annotations.Field.class) != null;
boolean isField = nextField.getAnnotation(org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField.class) != null;
isField |= nextField.getAnnotation(org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField.class) != null;
isField |= nextField.getAnnotation(org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField.class) != null;
Validate.isTrue(
hasEmbedded ||
hasColumn ||

View File

@ -1,26 +1,24 @@
{
"mappings" : {
"ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity" : {
"properties" : {
"codeable_concept_id" : {
"type" : "keyword"
},
"codingcode" : {
"type" : "keyword"
},
"codingcode_system_hash" : {
"type" : "keyword"
},
"codingdisplay" : {
"type" : "keyword"
},
"codingsystem" : {
"type" : "keyword"
},
"text" : {
"type" : "keyword"
}
}
}
}
"mappings": {
"properties": {
"codeable_concept_id": {
"type": "keyword"
},
"codingcode": {
"type": "keyword"
},
"codingcode_system_hash": {
"type": "keyword"
},
"codingdisplay": {
"type": "keyword"
},
"codingsystem": {
"type": "keyword"
},
"text": {
"type": "keyword"
}
}
}
}

View File

@ -1,6 +1,5 @@
{
"mappings" : {
"ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity" : {
"properties" : {
"codeconceptid" : {
"type" : "keyword"
@ -47,4 +46,3 @@
}
}
}
}

View File

@ -1,16 +1,19 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
@ -23,9 +26,11 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static ca.uhn.fhir.jpa.dao.BaseJpaTest.buildHeapLuceneHibernateSearchProperties;
import static org.junit.jupiter.api.Assertions.fail;
@Configuration
@ -148,12 +153,13 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", H2Dialect.class.getName());
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "local-heap");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.putAll(buildHeapLuceneHibernateSearchProperties());
return extraProperties;
}
/**
* Bean which validates incoming requests
*/

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender;
import ca.uhn.fhir.jpa.subscription.match.deliver.email.JavaMailEmailSender;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
@ -10,6 +10,10 @@ import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -151,10 +155,13 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", H2Dialect.class.getName());
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "local-heap");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true");
extraProperties.put(BackendSettings.backendKey(BackendSettings.TYPE), "lucene");
extraProperties.put(BackendSettings.backendKey(LuceneBackendSettings.ANALYSIS_CONFIGURER), HapiLuceneAnalysisConfigurer.class.getName());
extraProperties.put(BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_TYPE), "local-heap");
extraProperties.put(BackendSettings.backendKey(LuceneBackendSettings.LUCENE_VERSION), "LUCENE_CURRENT");
extraProperties.put(HibernateOrmMapperSettings.ENABLED, "true");
return extraProperties;
}

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.batch.svc.BatchJobSubmitterImpl;
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.bulk.svc.BulkExportDaoSvc;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
@ -15,6 +16,10 @@ import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -27,6 +32,7 @@ import java.sql.Connection;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static ca.uhn.fhir.jpa.dao.BaseJpaTest.buildHeapLuceneHibernateSearchProperties;
import static org.junit.jupiter.api.Assertions.fail;
@Configuration
@ -160,11 +166,8 @@ public class TestR4Config extends BaseJavaConfigR4 {
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", H2Dialect.class.getName());
extraProperties.put("hibernate.search.model_mapping", ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "local-heap");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true");
extraProperties.put("hibernate.temp.use_jdbc_metadata_defaults","false");
extraProperties.putAll(buildHeapLuceneHibernateSearchProperties());
return extraProperties;
}

View File

@ -1,77 +1,60 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus;
import org.hibernate.search.elasticsearch.cfg.IndexSchemaManagementStrategy;
import ca.uhn.fhir.jpa.search.lastn.config.TestElasticsearchContainerHelper;
import org.hibernate.search.backend.elasticsearch.index.IndexStatus;
import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pl.allegro.tech.embeddedelasticsearch.EmbeddedElastic;
import pl.allegro.tech.embeddedelasticsearch.PopularProperties;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Configuration
public class TestR4ConfigWithElasticSearch extends TestR4Config {
private static final Logger ourLog = LoggerFactory.getLogger(TestR4ConfigWithElasticSearch.class);
private static final String ELASTIC_VERSION = "6.5.4";
protected final String elasticsearchHost = "localhost";
protected final String elasticsearchUserId = "";
protected final String elasticsearchPassword = "";
@Override
@Bean
public Properties jpaProperties() {
Properties retVal = super.jpaProperties();
//Override default lucene settings
// Force elasticsearch to start first
int httpPort = embeddedElasticSearch().getHttpPort();
ourLog.info("ElasticSearch started on port: {}", httpPort);
int httpPort = elasticContainer().getMappedPort(9200);//9200 is the HTTP port
String host = elasticContainer().getHost();
new ElasticsearchHibernatePropertiesBuilder()
.setDebugRefreshAfterWrite(true)
.setDebugIndexSyncStrategy("read-sync")
.setDebugPrettyPrintJsonLog(true)
.setIndexSchemaManagementStrategy(IndexSchemaManagementStrategy.CREATE)
.setIndexSchemaManagementStrategy(SchemaManagementStrategyName.CREATE)
.setIndexManagementWaitTimeoutMillis(10000)
.setRequiredIndexStatus(ElasticsearchIndexStatus.YELLOW)
.setRestUrl("http://"+ elasticsearchHost + ":" + httpPort)
.setUsername(elasticsearchUserId)
.setPassword(elasticsearchPassword)
.setRequiredIndexStatus(IndexStatus.YELLOW)
.setRestUrl(host+ ":" + httpPort)
.setProtocol("http")
.setUsername("")
.setPassword("")
.apply(retVal);
return retVal;
}
@Bean
public EmbeddedElastic embeddedElasticSearch() {
EmbeddedElastic embeddedElastic = null;
try {
embeddedElastic = EmbeddedElastic.builder()
.withElasticVersion(ELASTIC_VERSION)
.withSetting(PopularProperties.TRANSPORT_TCP_PORT, 0)
.withSetting(PopularProperties.HTTP_PORT, 0)
.withSetting(PopularProperties.CLUSTER_NAME, UUID.randomUUID())
.withStartTimeout(60, TimeUnit.SECONDS)
.build()
.start();
} catch (IOException | InterruptedException e) {
throw new ConfigurationException(e);
}
return embeddedElastic;
public ElasticsearchContainer elasticContainer() {
ElasticsearchContainer embeddedElasticSearch = TestElasticsearchContainerHelper.getEmbeddedElasticSearch();
embeddedElasticSearch.start();
return embeddedElasticSearch;
}
@PreDestroy
public void stop() {
embeddedElasticSearch().stop();
elasticContainer().stop();
}
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -10,10 +11,17 @@ import java.io.IOException;
@Configuration
public class TestR4ConfigWithElasticsearchClient extends TestR4ConfigWithElasticSearch {
@Bean
public PartitionSettings partitionSettings() {
return new PartitionSettings();
}
@Bean()
public ElasticsearchSvcImpl myElasticsearchSvc() {
int elasticsearchPort = embeddedElasticSearch().getHttpPort();
return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUserId, elasticsearchPassword);
int elasticsearchPort = elasticContainer().getMappedPort(9200);
String host = elasticContainer().getHost();
return new ElasticsearchSvcImpl(host, elasticsearchPort, "", "");
}
@PreDestroy

View File

@ -3,14 +3,14 @@ package ca.uhn.fhir.jpa.config;
import java.util.Properties;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
@Configuration
@ -41,7 +41,7 @@ public class TestR4WithLuceneDisabledConfig extends TestR4Config {
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", H2Dialect.class.getName());
extraProperties.put("hibernate.search.autoregister_listeners", "false");
extraProperties.put(HibernateOrmMapperSettings.ENABLED, "false");
return extraProperties;
}

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
@ -10,6 +11,10 @@ import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -23,6 +28,7 @@ import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Properties;
import static ca.uhn.fhir.jpa.dao.BaseJpaTest.buildHeapLuceneHibernateSearchProperties;
import static org.junit.jupiter.api.Assertions.fail;
@Configuration
@ -146,10 +152,8 @@ public class TestR5Config extends BaseJavaConfigR5 {
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", H2Dialect.class.getName());
extraProperties.put("hibernate.search.model_mapping", ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "local-heap");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true");
extraProperties.putAll(buildHeapLuceneHibernateSearchProperties());
return extraProperties;
}

View File

@ -13,10 +13,14 @@ import ca.uhn.fhir.jpa.bulk.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
@ -24,6 +28,7 @@ import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
import ca.uhn.fhir.jpa.term.ValueSetExpansionR4Test;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
@ -45,6 +50,11 @@ import org.apache.commons.io.IOUtils;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.jdbc.Work;
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Resource;
@ -61,6 +71,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Pageable;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.PlatformTransactionManager;
@ -76,14 +87,18 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static ca.uhn.fhir.util.TestUtil.randomizeLocale;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.eq;
@ -111,6 +126,16 @@ public abstract class BaseJpaTest extends BaseTest {
TestUtil.setShouldRandomizeTimezones(false);
}
public static Map<?,?> buildHeapLuceneHibernateSearchProperties() {
Map<String, String> props = new HashMap<>();
props.put(BackendSettings.backendKey(BackendSettings.TYPE), "lucene");
props.put(BackendSettings.backendKey(LuceneBackendSettings.ANALYSIS_CONFIGURER), HapiLuceneAnalysisConfigurer.class.getName());
props.put(BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_TYPE), "local-heap");
props.put(BackendSettings.backendKey(LuceneBackendSettings.LUCENE_VERSION), "LUCENE_CURRENT");
props.put(HibernateOrmMapperSettings.ENABLED, "true");
return props;
}
@RegisterExtension
public LoggingExtension myLoggingExtension = new LoggingExtension();
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@ -580,4 +605,63 @@ public abstract class BaseJpaTest extends BaseTest {
Thread.sleep(500);
}
protected TermValueSetConceptDesignation assertTermConceptContainsDesignation(TermValueSetConcept theConcept, String theLanguage, String theUseSystem, String theUseCode, String theUseDisplay, String theDesignationValue) {
Stream<TermValueSetConceptDesignation> stream = theConcept.getDesignations().stream();
if (theLanguage != null) {
stream = stream.filter(designation -> theLanguage.equalsIgnoreCase(designation.getLanguage()));
}
if (theUseSystem != null) {
stream = stream.filter(designation -> theUseSystem.equalsIgnoreCase(designation.getUseSystem()));
}
if (theUseCode != null) {
stream = stream.filter(designation -> theUseCode.equalsIgnoreCase(designation.getUseCode()));
}
if (theUseDisplay != null) {
stream = stream.filter(designation -> theUseDisplay.equalsIgnoreCase(designation.getUseDisplay()));
}
if (theDesignationValue != null) {
stream = stream.filter(designation -> theDesignationValue.equalsIgnoreCase(designation.getValue()));
}
Optional<TermValueSetConceptDesignation> first = stream.findFirst();
if (!first.isPresent()) {
String failureMessage = String.format("Concept %s did not contain designation [%s|%s|%s|%s|%s] ", theConcept.toString(), theLanguage, theUseSystem, theUseCode, theUseDisplay, theDesignationValue);
fail(failureMessage);
return null;
} else {
return first.get();
}
}
protected TermValueSetConcept assertTermValueSetContainsConceptAndIsInDeclaredOrder(TermValueSet theValueSet, String theSystem, String theCode, String theDisplay, Integer theDesignationCount) {
List<TermValueSetConcept> contains = theValueSet.getConcepts();
Stream<TermValueSetConcept> stream = contains.stream();
if (theSystem != null) {
stream = stream.filter(concept -> theSystem.equalsIgnoreCase(concept.getSystem()));
}
if (theCode != null ) {
stream = stream.filter(concept -> theCode.equalsIgnoreCase(concept.getCode()));
}
if (theDisplay != null){
stream = stream.filter(concept -> theDisplay.equalsIgnoreCase(concept.getDisplay()));
}
if (theDesignationCount != null) {
stream = stream.filter(concept -> concept.getDesignations().size() == theDesignationCount);
}
Optional<TermValueSetConcept> first = stream.findFirst();
if (!first.isPresent()) {
String failureMessage = String.format("Expanded ValueSet %s did not contain concept [%s|%s|%s] with [%d] designations", theValueSet.getId(), theSystem, theCode, theDisplay, theDesignationCount);
fail(failureMessage);
return null;
} else {
TermValueSetConcept termValueSetConcept = first.get();
assertEquals(termValueSetConcept.getOrder(), theValueSet.getConcepts().indexOf(termValueSetConcept));
return termValueSetConcept;
}
}
}

View File

@ -62,8 +62,8 @@ import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@ -226,10 +226,10 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@BeforeEach
public void beforeFlushFT() {
runInTransaction(() -> {
FullTextEntityManager ftem = Search.getFullTextEntityManager(myEntityManager);
ftem.purgeAll(ResourceTable.class);
ftem.purgeAll(ResourceIndexedSearchParamString.class);
ftem.flushToIndexes();
SearchSession searchSession = Search.session(myEntityManager);
searchSession.workspace(ResourceTable.class).purge();
// searchSession.workspace(ResourceIndexedSearchParamString.class).purge();
searchSession.indexingPlan().execute();
});
myDaoConfig.setSchedulingDisabled(true);

View File

@ -16,7 +16,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.model.primitive.Base64BinaryDt;
@ -33,114 +32,6 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Test
public void testSuggestIgnoresBase64Content() {
Patient patient = new Patient();
patient.addName().addFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Media med = new Media();
med.getSubject().setReference(ptId);
med.getSubtype().setText("Systolic Blood Pressure");
med.getContent().setContentType("LCws");
med.getContent().setData(new Base64BinaryDt(new byte[] { 44, 44, 44, 44, 44, 44, 44, 44 }));
med.getContent().setTitle("bbbb syst");
myMediaDao.create(med, mySrd);
ourLog.info(myFhirCtx.newJsonParser().encodeResourceToString(med));
List<Suggestion> output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "press", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("Pressure", output.get(0).getTerm());
assertEquals("Systolic Blood Pressure", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "prezure", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("Pressure", output.get(0).getTerm());
assertEquals("Systolic Blood Pressure", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "syst", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("syst", output.get(0).getTerm());
assertEquals("bbbb syst", output.get(1).getTerm());
assertEquals("Systolic", output.get(2).getTerm());
assertEquals("Systolic Blood Pressure", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "LCws", null);
ourLog.info("Found: " + output);
assertEquals(0, output.size());
}
@Test
public void testSuggest() {
Patient patient = new Patient();
patient.addName().addFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReference(ptId);
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.create(obs, mySrd);
obs = new Observation();
obs.getSubject().setReference(ptId);
obs.getCode().setText("MNBVCXZ");
myObservationDao.create(obs, mySrd);
obs = new Observation();
obs.getSubject().setReference(ptId);
obs.getCode().setText("ZXC HELLO");
obs.addComponent().getCode().setText("HHHHHHHHHH");
myObservationDao.create(obs, mySrd);
/*
* These shouldn't match since they're for another patient
*/
patient = new Patient();
patient.addName().addFamily("testSuggest2");
IIdType ptId2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs2 = new Observation();
obs2.getSubject().setReference(ptId2);
obs2.getCode().setText("ZXCVBNMZZ");
myObservationDao.create(obs2, mySrd);
List<Suggestion> output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXCVBNM", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("ZXCVBNM", output.get(0).getTerm());
assertEquals("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL", output.get(1).getTerm());
assertEquals("ZXC", output.get(2).getTerm());
assertEquals("ZXC HELLO", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXC", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("ZXC", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
assertEquals("ZXCVBNM", output.get(2).getTerm());
assertEquals("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "HELO", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("HELLO", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "Z", null);
ourLog.info("Found: " + output);
assertEquals(0, output.size());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZX", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("ZXC", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
}
@Test
public void testSearchAndReindex() {
SearchParameterMap map;

View File

@ -1165,7 +1165,6 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest {
assertEquals("204 No Content", resp.getEntry().get(3).getResponse().getStatus());
}
Bundle respGetBundle = (Bundle) resp.getEntry().get(0).getResource();
assertEquals(1, respGetBundle.getEntry().size());
assertEquals("testTransactionOrdering" + pass, ((Patient) respGetBundle.getEntry().get(0).getResource()).getNameFirstRep().getFamilyFirstRep().getValue());

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
import ca.uhn.fhir.jpa.dao.data.ITagDefinitionDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao;
@ -54,8 +55,8 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.dstu3.model.AllergyIntolerance;
import org.hl7.fhir.dstu3.model.Appointment;
import org.hl7.fhir.dstu3.model.AuditEvent;
@ -319,6 +320,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Autowired
protected ITermCodeSystemDao myTermCodeSystemDao;
@Autowired
protected ITermCodeSystemVersionDao myTermCodeSystemVersionDao;
@Autowired
protected ITermReadSvc myTermSvc;
@Autowired
protected PlatformTransactionManager myTransactionMgr;
@ -375,10 +378,10 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@BeforeEach
public void beforeFlushFT() {
runInTransaction(() -> {
FullTextEntityManager ftem = Search.getFullTextEntityManager(myEntityManager);
ftem.purgeAll(ResourceTable.class);
ftem.purgeAll(ResourceIndexedSearchParamString.class);
ftem.flushToIndexes();
SearchSession searchSession = Search.session(myEntityManager);
searchSession.workspace(ResourceTable.class).purge();
// searchSession.workspace(ResourceIndexedSearchParamString.class).purge();
searchSession.indexingPlan().execute();
});
myDaoConfig.setSchedulingDisabled(true);

View File

@ -63,10 +63,24 @@ public class FhirResourceDaoDstu3CodeSystemTest extends BaseJpaDstu3Test {
cs.addConcept().setCode("A");
cs.addConcept().setCode("B");
myCodeSystemDao.update(cs, mySrd);
myTerminologyDeferredStorageSvc.saveAllDeferred();
runInTransaction(()->{
assertEquals(2, myConceptDao.count());
});
// Update the code system to reduce the count again
cs = new CodeSystem();
cs.setId(id);
cs.setUrl("http://foo");
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.addConcept().setCode("C");
myCodeSystemDao.update(cs, mySrd);
myTerminologyDeferredStorageSvc.saveAllDeferred();
runInTransaction(()->{
assertEquals(1, myConceptDao.count());
});
// Delete the code system
runInTransaction(()->{
myCodeSystemDao.delete(id);

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.StringAndListParam;
@ -141,116 +140,6 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test {
}
@Test
public void testSuggestIgnoresBase64Content() {
Patient patient = new Patient();
patient.addName().setFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless();
Media med = new Media();
med.getSubject().setReferenceElement(ptId);
med.getSubtype().setText("Systolic Blood Pressure");
med.getContent().setContentType("LCws");
med.getContent().setDataElement(new Base64BinaryType(new byte[] {44,44,44,44,44,44,44,44}));
med.getContent().setTitle("bbbb syst");
myMediaDao.create(med, mockSrd());
ourLog.info(myFhirCtx.newJsonParser().encodeResourceToString(med));
List<Suggestion> output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "press", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("Pressure", output.get(0).getTerm());
assertEquals("Systolic Blood Pressure", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "prezure", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("Pressure", output.get(0).getTerm());
assertEquals("Systolic Blood Pressure", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "syst", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("syst", output.get(0).getTerm());
assertEquals("bbbb syst", output.get(1).getTerm());
assertEquals("Systolic", output.get(2).getTerm());
assertEquals("Systolic Blood Pressure", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "LCws", null);
ourLog.info("Found: " + output);
assertEquals(0, output.size());
}
@Test
public void testSuggest() {
Patient patient = new Patient();
patient.addName().setFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReferenceElement(ptId);
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.create(obs, mockSrd());
obs = new Observation();
obs.getSubject().setReferenceElement(ptId);
obs.getCode().setText("MNBVCXZ");
myObservationDao.create(obs, mockSrd());
obs = new Observation();
obs.getSubject().setReferenceElement(ptId);
obs.getCode().setText("ZXC HELLO");
obs.addComponent().getCode().setText("HHHHHHHHHH");
myObservationDao.create(obs, mockSrd());
/*
* These shouldn't match since they're for another patient
*/
patient = new Patient();
patient.addName().setFamily("testSuggest2");
IIdType ptId2 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless();
Observation obs2 = new Observation();
obs2.getSubject().setReferenceElement(ptId2);
obs2.getCode().setText("ZXCVBNMZZ");
myObservationDao.create(obs2, mockSrd());
List<Suggestion> output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXCVBNM", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("ZXCVBNM", output.get(0).getTerm());
assertEquals("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL", output.get(1).getTerm());
assertEquals("ZXC", output.get(2).getTerm());
assertEquals("ZXC HELLO", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXC", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("ZXC", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
assertEquals("ZXCVBNM", output.get(2).getTerm());
assertEquals("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "HELO", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("HELLO", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "Z", null);
ourLog.info("Found: " + output);
assertEquals(0, output.size());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZX", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("ZXC", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
}
@Test
public void testSearchAndReindex() {
Patient patient;

View File

@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.jpa.util.CoordCalculatorTest;
import ca.uhn.fhir.jpa.util.SearchBox;
import org.hibernate.search.engine.spatial.GeoBoundingBox;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -41,8 +41,8 @@ public class PredicateBuilderCoordsTest {
@Test
public void testLongitudePredicateFromBox() {
SearchBox box = CoordCalculator.getBox(CoordCalculatorTest.LATITUDE_CHIN, CoordCalculatorTest.LONGITUDE_CHIN, CoordCalculatorTest.DISTANCE_TAVEUNI);
assertThat(box.getNorthEast().getLongitude(), greaterThan(box.getSouthWest().getLongitude()));
GeoBoundingBox box = CoordCalculator.getBox(CoordCalculatorTest.LATITUDE_CHIN, CoordCalculatorTest.LONGITUDE_CHIN, CoordCalculatorTest.DISTANCE_TAVEUNI);
assertThat(box.bottomRight().longitude(), greaterThan(box.topLeft().longitude()));
ArgumentCaptor<Predicate> andLeft = ArgumentCaptor.forClass(Predicate.class);
ArgumentCaptor<Predicate> andRight = ArgumentCaptor.forClass(Predicate.class);
@ -58,15 +58,15 @@ public class PredicateBuilderCoordsTest {
verify(myBuilder).and(andLeft.capture(), andRight.capture());
assertEquals(andLeft.getValue(), gte);
assertEquals(andRight.getValue(), lte);
assertEquals(gteValue.getValue(), box.getSouthWest().getLongitude());
assertEquals(lteValue.getValue(), box.getNorthEast().getLongitude());
assertEquals(gteValue.getValue(), box.topLeft().longitude());
assertEquals(lteValue.getValue(), box.bottomRight().longitude());
}
@Test
public void testAntiMeridianLongitudePredicateFromBox() {
SearchBox box = CoordCalculator.getBox(CoordCalculatorTest.LATITUDE_TAVEUNI, CoordCalculatorTest.LONGITIDE_TAVEUNI, CoordCalculatorTest.DISTANCE_TAVEUNI);
assertThat(box.getNorthEast().getLongitude(), lessThan(box.getSouthWest().getLongitude()));
assertTrue(box.crossesAntiMeridian());
GeoBoundingBox box = CoordCalculator.getBox(CoordCalculatorTest.LATITUDE_TAVEUNI, CoordCalculatorTest.LONGITIDE_TAVEUNI, CoordCalculatorTest.DISTANCE_TAVEUNI);
assertThat(box.bottomRight().longitude(), lessThan(box.topLeft().longitude()));
assertTrue(box.bottomRight().longitude() < box.topLeft().longitude());
ArgumentCaptor<Predicate> orLeft = ArgumentCaptor.forClass(Predicate.class);
ArgumentCaptor<Predicate> orRight = ArgumentCaptor.forClass(Predicate.class);
@ -82,8 +82,8 @@ public class PredicateBuilderCoordsTest {
verify(myBuilder).or(orLeft.capture(), orRight.capture());
assertEquals(orLeft.getValue(), gte);
assertEquals(orRight.getValue(), lte);
assertEquals(gteValue.getValue(), box.getNorthEast().getLongitude());
assertEquals(lteValue.getValue(), box.getSouthWest().getLongitude());
assertEquals(gteValue.getValue(), box.bottomRight().longitude());
assertEquals(lteValue.getValue(), box.topLeft().longitude());
}
}

View File

@ -54,6 +54,9 @@ import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@ -68,6 +71,7 @@ import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl;
import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl;
import ca.uhn.fhir.jpa.term.ValueSetExpansionR4Test;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
@ -87,8 +91,8 @@ import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -159,6 +163,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.AopTestUtils;
@ -170,11 +175,14 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
@ -524,10 +532,10 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
@BeforeEach
public void beforeFlushFT() {
runInTransaction(() -> {
FullTextEntityManager ftem = Search.getFullTextEntityManager(myEntityManager);
ftem.purgeAll(ResourceTable.class);
ftem.purgeAll(ResourceIndexedSearchParamString.class);
ftem.flushToIndexes();
SearchSession searchSession = Search.session(myEntityManager);
searchSession.workspace(ResourceTable.class).purge();
// searchSession.workspace(ResourceIndexedSearchParamString.class).purge();
searchSession.indexingPlan().execute();
});
myDaoConfig.setSchedulingDisabled(true);
@ -751,4 +759,68 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
return uuid;
}
protected ValueSet.ConceptReferenceDesignationComponent assertConceptContainsDesignation(ValueSet.ValueSetExpansionContainsComponent theConcept, String theLanguage, String theUseSystem, String theUseCode, String theUseDisplay, String theDesignationValue) {
Stream<ValueSet.ConceptReferenceDesignationComponent> stream = theConcept.getDesignation().stream();
if (theLanguage != null) {
stream = stream.filter(designation -> theLanguage.equalsIgnoreCase(designation.getLanguage()));
}
if (theUseSystem != null) {
stream = stream.filter(designation -> theUseSystem.equalsIgnoreCase(designation.getUse().getSystem()));
}
if (theUseCode != null) {
stream = stream.filter(designation -> theUseCode.equalsIgnoreCase(designation.getUse().getCode()));
}
if (theUseDisplay != null) {
stream = stream.filter(designation -> theUseDisplay.equalsIgnoreCase(designation.getUse().getDisplay()));
}
if (theDesignationValue != null) {
stream = stream.filter(designation -> theDesignationValue.equalsIgnoreCase(designation.getValue()));
}
Optional<ValueSet.ConceptReferenceDesignationComponent> first = stream.findFirst();
if (!first.isPresent()) {
String failureMessage = String.format("Concept %s did not contain designation [%s|%s|%s|%s|%s] ", theConcept.toString(), theLanguage, theUseSystem, theUseCode, theUseDisplay, theDesignationValue);
fail(failureMessage);
return null;
} else {
return first.get();
}
}
protected ValueSet.ValueSetExpansionContainsComponent assertExpandedValueSetContainsConcept(ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Integer theDesignationCount) {
List<ValueSet.ValueSetExpansionContainsComponent> contains = theValueSet.getExpansion().getContains();
Stream<ValueSet.ValueSetExpansionContainsComponent> stream = contains.stream();
if (theSystem != null) {
stream = stream.filter(concept -> theSystem.equalsIgnoreCase(concept.getSystem()));
}
if (theCode != null ) {
stream = stream.filter(concept -> theCode.equalsIgnoreCase(concept.getCode()));
}
if (theDisplay != null){
stream = stream.filter(concept -> theDisplay.equalsIgnoreCase(concept.getDisplay()));
}
if (theDesignationCount != null) {
stream = stream.filter(concept -> concept.getDesignation().size() == theDesignationCount);
}
Optional<ValueSet.ValueSetExpansionContainsComponent> first = stream.findFirst();
if (!first.isPresent()) {
String failureMessage = String.format("Expanded ValueSet %s did not contain concept [%s|%s|%s] with [%d] designations", theValueSet.getId(), theSystem, theCode, theDisplay, theDesignationCount);
fail(failureMessage);
return null;
} else {
return first.get();
}
}
public List<String> getExpandedConceptsByValueSetUrl(String theValuesetUrl) {
return runInTransaction(() -> {
List<TermValueSet> valueSets = myTermValueSetDao.findTermValueSetByUrl(Pageable.unpaged(), theValuesetUrl);
assertEquals(1, valueSets.size());
TermValueSet valueSet = valueSets.get(0);
List<TermValueSetConcept> concepts = valueSet.getConcepts();
return concepts.stream().map(concept -> concept.getCode()).collect(Collectors.toList());
});
}
}

View File

@ -59,7 +59,7 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test {
// Now the background scheduler will do its thing
myTerminologyDeferredStorageSvc.saveDeferred();
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertEquals(0, myTermCodeSystemDao.count());
assertEquals(0, myTermCodeSystemVersionDao.count());
assertEquals(0, myTermConceptDao.count());
});
@ -153,7 +153,7 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test {
// The remaining versions and Code System entities should be gone now.
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertEquals(0, myTermCodeSystemDao.count());
assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo"));
assertEquals(0, myTermCodeSystemVersionDao.count());
List<ResourceTable> resourceList = myResourceTableDao.findAll();

View File

@ -47,7 +47,6 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
runInTransaction(() -> {
ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get();
assertNotNull(resourceTable.getDeleted());
assertTrue(resourceTable.isDeleted());
});
// Current version should be marked as deleted

View File

@ -1,16 +1,13 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -122,129 +119,6 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
}
@Test
public void testSuggestIgnoresBase64Content() {
Patient patient = new Patient();
patient.addName().setFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless();
// Attached to patient
Observation obs1 = new Observation();
obs1.setSubject(new Reference(ptId));
obs1.getCode().setText("AAAAA");
obs1.setValue(new StringType("Systolic Blood Pressure"));
obs1.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs1, mockSrd()).getId().toUnqualifiedVersionless();
// Not attached to patient
Observation obs2 = new Observation();
obs2.getCode().setText("AAAAA");
obs2.setValue(new StringType("Diastolic Blood Pressure"));
obs2.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs2, mockSrd()).getId().toUnqualifiedVersionless();
Media med = new Media();
med.getSubject().setReferenceElement(ptId);
med.getContent().setContentType("LCws");
med.getContent().setDataElement(new Base64BinaryType(new byte[]{44, 44, 44, 44, 44, 44, 44, 44}));
med.getContent().setTitle("bbbb syst");
myMediaDao.create(med, mockSrd());
ourLog.info(myFhirCtx.newJsonParser().encodeResourceToString(med));
List<Suggestion> output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "press", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("Pressure", output.get(0).getTerm());
assertEquals("Systolic Blood Pressure", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "prezure", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("Pressure", output.get(0).getTerm());
assertEquals("Systolic Blood Pressure", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "syst", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("syst", output.get(0).getTerm());
assertEquals("bbbb syst", output.get(1).getTerm());
assertEquals("Systolic", output.get(2).getTerm());
assertEquals("Systolic Blood Pressure", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "LCws", null);
ourLog.info("Found: " + output);
assertEquals(0, output.size());
}
@Test
public void testSuggest() {
Patient patient = new Patient();
patient.addName().setFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReferenceElement(ptId);
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.create(obs, mockSrd());
obs = new Observation();
obs.getSubject().setReferenceElement(ptId);
obs.getCode().setText("MNBVCXZ");
myObservationDao.create(obs, mockSrd());
obs = new Observation();
obs.getSubject().setReferenceElement(ptId);
obs.getCode().setText("ZXC HELLO");
obs.addComponent().getCode().setText("HHHHHHHHHH");
myObservationDao.create(obs, mockSrd());
/*
* These shouldn't match since they're for another patient
*/
patient = new Patient();
patient.addName().setFamily("testSuggest2");
IIdType ptId2 = myPatientDao.create(patient, mockSrd()).getId().toUnqualifiedVersionless();
Observation obs2 = new Observation();
obs2.getSubject().setReferenceElement(ptId2);
obs2.getCode().setText("ZXCVBNMZZ");
myObservationDao.create(obs2, mockSrd());
List<Suggestion> output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXCVBNM", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("ZXCVBNM", output.get(0).getTerm());
assertEquals("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL", output.get(1).getTerm());
assertEquals("ZXC", output.get(2).getTerm());
assertEquals("ZXC HELLO", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZXC", null);
ourLog.info("Found: " + output);
assertEquals(4, output.size());
assertEquals("ZXC", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
assertEquals("ZXCVBNM", output.get(2).getTerm());
assertEquals("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL", output.get(3).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "HELO", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("HELLO", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "Z", null);
ourLog.info("Found: " + output);
assertEquals(0, output.size());
output = mySearchDao.suggestKeywords("Patient/" + ptId.getIdPart() + "/$everything", "_content", "ZX", null);
ourLog.info("Found: " + output);
assertEquals(2, output.size());
assertEquals("ZXC", output.get(0).getTerm());
assertEquals("ZXC HELLO", output.get(1).getTerm());
}
@Test
public void testSearchAndReindex() {
Patient patient;

View File

@ -1,6 +1,5 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.ReferenceParam;

View File

@ -6,7 +6,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.JpaResourceDao;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
@ -50,6 +50,8 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -125,6 +127,7 @@ import java.util.concurrent.Future;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
@ -286,8 +289,42 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, tableOpt.get().getIndexStatus().longValue());
assertThat(myResourceIndexedSearchParamTokenDao.countForResourceId(id1.getIdPartAsLong()), not(greaterThan(0)));
});
}
@Test
public void testTermConceptReindexingDoesntDuplicateData() {
myDaoConfig.setSchedulingDisabled(true);
CodeSystem cs = new CodeSystem();
cs.setId("nhin-use");
cs.setUrl("http://zoop.com");
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.addConcept().setCode("zoop1").setDisplay("zoop_disp1").setDefinition("zoop_defi1");
cs.addConcept().setCode("zoop2").setDisplay("zoop_disp2").setDefinition("zoop_defi2");
cs.addConcept().setCode("zoop3").setDisplay("zoop_disp3").setDefinition("zoop_defi3");
IIdType id1 = myCodeSystemDao.create(cs).getId().toUnqualifiedVersionless();
runInTransaction(() -> {
assertEquals(3L, myTermConceptDao.count());
SearchSession session = Search.session(myEntityManager);
List<TermConcept> termConcepts = session.search(TermConcept.class).where(f -> f.matchAll()).fetchAllHits();
assertEquals(3, termConcepts.size());
});
myResourceReindexingSvc.markAllResourcesForReindexing();
myResourceReindexingSvc.forceReindexingPass();
myTerminologyDeferredStorageSvc.saveAllDeferred();
runInTransaction(() -> {
assertEquals(3L, myTermConceptDao.count());
SearchSession session = Search.session(myEntityManager);
List<TermConcept> termConcepts = session.search(TermConcept.class).where(f -> f.matchAll()).fetchAllHits();
assertEquals(3, termConcepts.size());
});
}
@Test
@ -3948,7 +3985,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
String uuid = UUID.randomUUID().toString();
runInTransaction(() -> {
Search search = new Search();
ca.uhn.fhir.jpa.entity.Search search = new ca.uhn.fhir.jpa.entity.Search();
SearchCoordinatorSvcImpl.populateSearchEntity(map, "Encounter", uuid, normalized, search, RequestPartitionId.allPartitions());
search.setStatus(SearchStatusEnum.FAILED);
search.setFailureCode(500);

View File

@ -45,6 +45,8 @@ import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDao;
import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDesignationDao;
import ca.uhn.fhir.jpa.dao.data.ITermValueSetDao;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
@ -58,6 +60,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl;
import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl;
import ca.uhn.fhir.jpa.term.ValueSetExpansionR4Test;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
@ -73,8 +76,8 @@ import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.AllergyIntolerance;
@ -137,6 +140,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.AopTestUtils;
@ -149,7 +153,11 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
@ -454,10 +462,10 @@ public abstract class BaseJpaR5Test extends BaseJpaTest {
@BeforeEach
public void beforeFlushFT() {
runInTransaction(() -> {
FullTextEntityManager ftem = Search.getFullTextEntityManager(myEntityManager);
ftem.purgeAll(ResourceTable.class);
ftem.purgeAll(ResourceIndexedSearchParamString.class);
ftem.flushToIndexes();
SearchSession searchSession = Search.session(myEntityManager);
searchSession.workspace(ResourceTable.class).purge();
// searchSession.workspace(ResourceIndexedSearchParamString.class).purge();
searchSession.indexingPlan().execute();
});
myDaoConfig.setSchedulingDisabled(true);
@ -626,5 +634,40 @@ public abstract class BaseJpaR5Test extends BaseJpaTest {
String[] uuidParams = params.get(Constants.PARAM_PAGINGACTION);
return uuidParams[0];
}
protected ValueSet.ValueSetExpansionContainsComponent assertExpandedValueSetContainsConcept(ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Integer theDesignationCount) {
List<ValueSet.ValueSetExpansionContainsComponent> contains = theValueSet.getExpansion().getContains();
Stream<ValueSet.ValueSetExpansionContainsComponent> stream = contains.stream();
if (theSystem != null) {
stream = stream.filter(concept -> theSystem.equalsIgnoreCase(concept.getSystem()));
}
if (theCode != null ) {
stream = stream.filter(concept -> theCode.equalsIgnoreCase(concept.getCode()));
}
if (theDisplay != null){
stream = stream.filter(concept -> theDisplay.equalsIgnoreCase(concept.getDisplay()));
}
if (theDesignationCount != null) {
stream = stream.filter(concept -> concept.getDesignation().size() == theDesignationCount);
}
Optional<ValueSet.ValueSetExpansionContainsComponent> first = stream.findFirst();
if (!first.isPresent()) {
String failureMessage = String.format("Expanded ValueSet %s did not contain concept [%s|%s|%s] with [%d] designations", theValueSet.getId(), theSystem, theCode, theDisplay, theDesignationCount);
fail(failureMessage);
return null;
} else {
return first.get();
}
}
public List<String> getExpandedConceptsByValueSetUrl(String theValuesetUrl) {
return runInTransaction(() -> {
List<TermValueSet> valueSets = myTermValueSetDao.findTermValueSetByUrl(Pageable.unpaged(), theValuesetUrl);
assertEquals(1, valueSets.size());
TermValueSet valueSet = valueSets.get(0);
List<TermValueSetConcept> concepts = valueSet.getConcepts();
return concepts.stream().map(concept -> concept.getCode()).collect(Collectors.toList());
});
}
}

View File

@ -43,7 +43,7 @@ public class FhirResourceDaoR5CodeSystemTest extends BaseJpaR5Test {
// Now the background scheduler will do its thing
myTermDeferredStorageSvc.saveDeferred();
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertEquals(0, myTermCodeSystemDao.count());
assertEquals(0, myTermCodeSystemVersionDao.count());
assertEquals(0, myTermConceptDao.count());
});
@ -137,7 +137,7 @@ public class FhirResourceDaoR5CodeSystemTest extends BaseJpaR5Test {
// The remaining versions and Code System entities should be gone now.
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertEquals(0, myTermCodeSystemDao.count());
assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo"));
assertEquals(0, myTermCodeSystemVersionDao.count());
List<ResourceTable> resourceList = myResourceTableDao.findAll();

View File

@ -194,99 +194,6 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
}
}
@Transactional(propagation = Propagation.NEVER)
@Test
public void testSuggestKeywords() throws Exception {
Patient patient = new Patient();
patient.addName().addFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
obs.getSubject().setReference(ptId);
IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.setId(obsId);
obs.getSubject().setReference(ptId);
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.update(obs, mySrd);
// Try to wait for the indexing to complete
waitForSize(2, ()-> fetchSuggestionCount(ptId));
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml");
try (CloseableHttpResponse http = ourHttpClient.execute(get)) {
assertEquals(200, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
Parameters parameters = ourCtx.newXmlParser().parseResource(Parameters.class, output);
assertEquals(2, parameters.getParameter().size());
assertEquals("keyword", parameters.getParameter().get(0).getPart().get(0).getName());
assertEquals(new StringDt("ZXCVBNM"), parameters.getParameter().get(0).getPart().get(0).getValue());
assertEquals("score", parameters.getParameter().get(0).getPart().get(1).getName());
assertEquals(new DecimalDt("1.0"), parameters.getParameter().get(0).getPart().get(1).getValue());
}
}
private Number fetchSuggestionCount(IIdType thePtId) throws IOException {
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + thePtId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml");
try (CloseableHttpResponse http = ourHttpClient.execute(get)) {
assertEquals(200, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
Parameters parameters = ourCtx.newXmlParser().parseResource(Parameters.class, output);
return parameters.getParameter().size();
}
}
@Test
public void testSuggestKeywordsInvalid() throws Exception {
Patient patient = new Patient();
patient.addName().addFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReference(ptId);
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.create(obs, mySrd);
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
assertEquals(400, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
assertThat(output, containsString("Parameter 'context' must be provided"));
} finally {
http.close();
}
get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything");
http = ourHttpClient.execute(get);
try {
assertEquals(400, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
assertThat(output, containsString("Parameter 'searchParam' must be provided"));
} finally {
http.close();
}
get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything&searchParam=aa");
http = ourHttpClient.execute(get);
try {
assertEquals(400, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
assertThat(output, containsString("Parameter 'text' must be provided"));
} finally {
http.close();
}
}
@Test
public void testGetOperationDefinition() {

View File

@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@ -35,7 +36,11 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst
@Test
public void testLookupOnExternalCode() {
ResourceProviderDstu3ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd);
ResourceProviderDstu3ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd, myCaptureQueriesListener);
runInTransaction(()->{
ourLog.info("Code system versions:\n * " + myTermCodeSystemVersionDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
Parameters respParam = ourClient
.operation()

View File

@ -7,6 +7,8 @@ import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.util.BaseCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
@ -949,6 +951,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
public static CodeSystem createExternalCs(IFhirResourceDao<CodeSystem> theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails) {
return createExternalCs(theCodeSystemDao, theResourceTableDao, theTermCodeSystemStorageSvc, theRequestDetails, null);
}
@Nonnull
public static CodeSystem createExternalCs(IFhirResourceDao<CodeSystem> theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails, CircularQueueCaptureQueriesListener theCaptureQueriesListener) {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
@ -979,7 +986,15 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B");
cs.getConcepts().add(parentB);
ourLog.info("About to update CodeSystem");
if (theCaptureQueriesListener != null) {
theCaptureQueriesListener.clear();
}
theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
if (theCaptureQueriesListener != null) {
theCaptureQueriesListener.logAllQueries();
}
return codeSystem;
}

View File

@ -50,6 +50,8 @@ import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest.URL_
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -1110,68 +1112,24 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration", 2);
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter", 0);
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum", 1);
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum", 0);
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
});
}
private void validateTermValueSetExpandedAndChildrenV2(String theValueSetName, CodeSystem theCodeSystem) {
runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable_v2);
assertTrue(optionalValueSetByResourcePid.isPresent());
@ -1187,64 +1145,22 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration v2", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration v2", 2);
assertThat(concept.getSystemVersion(), is(equalTo("2")));
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter v2", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
TermValueSetConcept termValueSetConcept1 = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter v2", 0);
assertThat(termValueSetConcept1.getSystemVersion(), is(equalTo("2")));
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum v2", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum v2", 1);
assertThat(otherConcept.getSystemVersion(), is(equalTo("2")));
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum v2", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
TermValueSetConcept termValueSetConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum v2", 0);
assertThat(termValueSetConcept.getSystemVersion(), is(equalTo("2")));
});
}

View File

@ -38,6 +38,7 @@ import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
@ -53,8 +54,11 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.slf4j.LoggerFactory.getLogger;
public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
private static final Logger ourLog = getLogger(BaseResourceProviderR4Test.class);
protected static IValidationSupport myValidationSupport;
protected static CloseableHttpClient ourHttpClient;

View File

@ -58,7 +58,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.leftPad;
@ -561,8 +560,8 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
assertEquals(1, response.getEntry().size());
assertNull(response.getTotalElement().getValue());
runInTransaction(()->{
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(searchId).orElseThrow(()->new IllegalStateException());
runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(searchId).orElseThrow(() -> new IllegalStateException());
assertEquals(3, search.getNumFound());
assertEquals(1, search.getNumBlocked());
assertEquals(2, search.getTotalCount());
@ -577,7 +576,8 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
*/
@Test
public void testDefaultInterceptorAllowsAll() {
myConsentInterceptor = new ConsentInterceptor(new IConsentService() {});
myConsentInterceptor = new ConsentInterceptor(new IConsentService() {
});
ourRestServer.getInterceptorService().registerInterceptor(myConsentInterceptor);
myClient.create().resource(new Patient().setGender(Enumerations.AdministrativeGender.MALE).addName(new HumanName().setFamily("1"))).execute();
@ -598,8 +598,8 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
// The paging should have ended now - but the last redacted female result is an empty existing page which should never have been there.
assertNotNull(BundleUtil.getLinkUrlOfType(myFhirCtx, response, "next"));
runInTransaction(()->{
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(searchId).orElseThrow(()->new IllegalStateException());
runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(searchId).orElseThrow(() -> new IllegalStateException());
assertEquals(3, search.getNumFound());
assertEquals(0, search.getNumBlocked());
assertEquals(3, search.getTotalCount());
@ -611,7 +611,8 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
*/
@Test
public void testDefaultInterceptorAllowsFailure() {
myConsentInterceptor = new ConsentInterceptor(new IConsentService() {});
myConsentInterceptor = new ConsentInterceptor(new IConsentService() {
});
ourRestServer.getInterceptorService().registerInterceptor(myConsentInterceptor);
myClient.create().resource(new Patient().setGender(Enumerations.AdministrativeGender.MALE).addName(new HumanName().setFamily("1"))).execute();

View File

@ -55,7 +55,9 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
@Test
public void testLookupOnExternalCode() {
myCaptureQueriesListener.clear();
ResourceProviderR4ValueSetNoVerCSNoVerTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd);
myCaptureQueriesListener.logAllQueriesForCurrentThread();
Parameters respParam = myClient
.operation()

View File

@ -791,7 +791,9 @@ public class ResourceProviderR4CodeSystemVersionedTest extends BaseResourceProvi
String encoded = myFhirCtx.newJsonParser().encodeResourceToString(initialCodeSystem);
HttpPut putRequest = new HttpPut(ourServerBase + "/CodeSystem/" + parentChildCs1Id);
putRequest.setEntity(new StringEntity(encoded, ContentType.parse("application/json+fhir")));
myCaptureQueriesListener.clear();
CloseableHttpResponse resp = ourHttpClient.execute(putRequest);
myCaptureQueriesListener.logAllQueries();
try {
assertEquals(200, resp.getStatusLine().getStatusCode());
} finally {

View File

@ -61,6 +61,7 @@ import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -540,9 +541,10 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info("zoop");
ourLog.info(resp);
assertThat(resp, containsStringIgnoringCase("<code value=\"M\"/>"));
assertThat(resp, is(containsStringIgnoringCase("<code value=\"M\"/>")));
}
@Test
@ -916,60 +918,19 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration", 2);
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter", 0);
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum", 1);
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum", 0);
});
}

View File

@ -815,60 +815,19 @@ public class ResourceProviderR4ValueSetVerCSNoVerTest extends BaseResourceProvid
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration", 2);
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter", 0);
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum", 1);
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum", 0);
});
}

View File

@ -1,6 +1,5 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
@ -9,14 +8,10 @@ import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -41,7 +36,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
@ -49,9 +43,7 @@ import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET;
@ -59,6 +51,8 @@ import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -117,7 +111,7 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
theCodeSystem.setId("CodeSystem/cs2");
theCodeSystem.setVersion("2");
for(CodeSystem.ConceptDefinitionComponent conceptDefinitionComponent : theCodeSystem.getConcept()) {
for (CodeSystem.ConceptDefinitionComponent conceptDefinitionComponent : theCodeSystem.getConcept()) {
conceptDefinitionComponent.setDisplay(conceptDefinitionComponent.getDisplay() + " v2");
}
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@ -940,7 +934,7 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
ourLog.info("Updated ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedValueSet_v1));
String updatedValueSetName_v1 = valueSet_v1.getName();
validateTermValueSetNotExpanded(updatedValueSetName_v1,"1", myExtensionalVsIdOnResourceTable_v1);
validateTermValueSetNotExpanded(updatedValueSetName_v1, "1", myExtensionalVsIdOnResourceTable_v1);
ValueSet updatedValueSet_v2 = valueSet_v2;
updatedValueSet_v2.setName(valueSet_v2.getName().concat(" - MODIFIED"));
@ -949,7 +943,7 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
ourLog.info("Updated ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedValueSet_v2));
String updatedValueSetName_v2 = valueSet_v2.getName();
validateTermValueSetNotExpanded(updatedValueSetName_v2,"2", myExtensionalVsIdOnResourceTable_v2);
validateTermValueSetNotExpanded(updatedValueSetName_v2, "2", myExtensionalVsIdOnResourceTable_v2);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
validateTermValueSetExpandedAndChildrenV1(updatedValueSetName_v1, codeSystem_v1);
@ -1066,64 +1060,19 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration", 2);
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter", 0);
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum", 1);
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum", 0);
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
});
}
@ -1143,64 +1092,22 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration v2", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration v2", 2);
assertThat(concept.getSystemVersion(), is(equalTo("2")));
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter v2", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
TermValueSetConcept termValueSetConcept1 = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter v2", 0);
assertThat(termValueSetConcept1.getSystemVersion(), is(equalTo("2")));
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum v2", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum v2", 1);
assertThat(otherConcept.getSystemVersion(), is(equalTo("2")));
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum v2", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
TermValueSetConcept termValueSetConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum v2", 0);
assertThat(termValueSetConcept.getSystemVersion(), is(equalTo("2")));
});
}
@ -1747,7 +1654,7 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
private boolean clearDeferredStorageQueue() {
if(!myTerminologyDeferredStorageSvc.isStorageQueueEmpty()) {
if (!myTerminologyDeferredStorageSvc.isStorageQueueEmpty()) {
myTerminologyDeferredStorageSvc.saveAllDeferred();
return false;
} else {
@ -1762,8 +1669,8 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
try {
persistLocalVs(createLocalVs(URL_MY_CODE_SYSTEM, "1"));
fail();
} catch (UnprocessableEntityException theE) {
assertThat(theE.getMessage(), containsString("Can not create multiple ValueSet resources with ValueSet.url \"" + URL_MY_VALUE_SET + "\" and ValueSet.version \"1\", already have one with resource ID: "));
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Can not create multiple ValueSet resources with ValueSet.url \"" + URL_MY_VALUE_SET + "\" and ValueSet.version \"1\", already have one with resource ID: "));
}
}

View File

@ -305,104 +305,6 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
assertThat(http.getFirstHeader("Content-Type").getValue(), containsString("application/fhir+json"));
}
@Transactional(propagation = Propagation.NEVER)
@Test
public void testSuggestKeywords() throws Exception {
Patient patient = new Patient();
patient.addName().setFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
obs.getSubject().setReferenceElement(ptId);
IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.setId(obsId);
obs.getSubject().setReferenceElement(ptId);
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.update(obs, mySrd);
// Try to wait for the indexing to complete
waitForSize(2, () -> fetchSuggestionCount(ptId));
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
assertEquals(200, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
Parameters parameters = ourCtx.newXmlParser().parseResource(Parameters.class, output);
assertEquals(2, parameters.getParameter().size());
assertEquals("keyword", parameters.getParameter().get(0).getPart().get(0).getName());
assertEquals(("ZXCVBNM"), ((StringType) parameters.getParameter().get(0).getPart().get(0).getValue()).getValueAsString());
assertEquals("score", parameters.getParameter().get(0).getPart().get(1).getName());
assertEquals(("1.0"), ((DecimalType) parameters.getParameter().get(0).getPart().get(1).getValue()).getValueAsString());
} finally {
http.close();
}
}
private Number fetchSuggestionCount(IIdType thePtId) throws IOException {
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + thePtId.getIdPart() + "/$everything&searchParam=_content&text=zxc&_pretty=true&_format=xml");
try (CloseableHttpResponse http = ourHttpClient.execute(get)) {
assertEquals(200, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
Parameters parameters = ourCtx.newXmlParser().parseResource(Parameters.class, output);
return parameters.getParameter().size();
}
}
@Test
public void testSuggestKeywordsInvalid() throws Exception {
Patient patient = new Patient();
patient.addName().setFamily("testSuggest");
IIdType ptId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReferenceElement(ptId);
obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL");
myObservationDao.create(obs, mySrd);
HttpGet get = new HttpGet(ourServerBase + "/$suggest-keywords");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
assertEquals(400, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
assertThat(output, containsString("Parameter 'context' must be provided"));
} finally {
http.close();
}
get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything");
http = ourHttpClient.execute(get);
try {
assertEquals(400, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
assertThat(output, containsString("Parameter 'searchParam' must be provided"));
} finally {
http.close();
}
get = new HttpGet(ourServerBase + "/$suggest-keywords?context=Patient/" + ptId.getIdPart() + "/$everything&searchParam=aa");
http = ourHttpClient.execute(get);
try {
assertEquals(400, http.getStatusLine().getStatusCode());
String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(output);
assertThat(output, containsString("Parameter 'text' must be provided"));
} finally {
http.close();
}
}
@Test
public void testTransactionCount() throws Exception {
for (int i = 0; i < 20; i++) {

View File

@ -47,15 +47,20 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -293,6 +298,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
List<String> expandedConceptsByValueSetUrl = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
.onInstance(myExtensionalVsId)
@ -304,6 +310,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
@ -311,13 +318,14 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConceptsByValueSetUrl.subList(1,24))));
}
@Nonnull
public List<String> toCodes(ValueSet theExpandedValueSet) {
return theExpandedValueSet.getExpansion().getContains().stream().map(t -> t.getCode()).collect(Collectors.toList());
}
@Test
@ -326,7 +334,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
List<String> expandedConcepts = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
.onInstance(myExtensionalVsId)
@ -345,9 +353,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConcepts.subList(0,1))));
}
@ -358,6 +364,8 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
List<String> expandedConcepts = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
.onInstance(myExtensionalVsId)
@ -377,10 +385,8 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConcepts.subList(1,2))));
}
@Test
@ -512,6 +518,8 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
List<String> expandedConcepts = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
@ -531,13 +539,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConcepts.subList(1,24))));
}
@Test
@ -546,6 +548,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
List<String> expandedConcepts = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
@ -566,10 +569,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConcepts.subList(0,1))));
}
@Test
@ -579,6 +579,8 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
List<String> expandedConcepts = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
@ -599,9 +601,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConcepts.subList(1,2))));
}
@ -681,6 +681,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
List<String> expandedConcepts = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
@ -700,12 +701,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConcepts.subList(1,24))));
}
@ -718,6 +714,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
List<String> expandedConcepts = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
@ -737,9 +734,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConcepts.subList(0,1))));
}
@ -752,6 +747,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
List<String> expandedConcepts = getExpandedConceptsByValueSetUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
@ -772,10 +768,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
assertThat(toCodes(expanded), is(equalTo(expandedConcepts.subList(1,2))));
}
@Test
@ -1141,67 +1134,17 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertTrue(optionalValueSetByUrl.isPresent());
TermValueSet termValueSet = optionalValueSetByUrl.get();
assertSame(optionalValueSetByResourcePid.get(), termValueSet);
ourLog.info("ValueSet:\n" + termValueSet.toString());
assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", termValueSet.getUrl());
assertEquals(theValueSetName, termValueSet.getName());
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration", 2);
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter", 0);
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum", 1);
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum", 0);
});
}

View File

@ -1097,64 +1097,18 @@ public class ResourceProviderR5ValueSetVersionedTest extends BaseResourceProvide
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration", 2);
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter", 0);
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum", 1);
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("1", concept.getSystemVersion());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum", 0);
});
}
@ -1174,64 +1128,18 @@ public class ResourceProviderR5ValueSetVersionedTest extends BaseResourceProvide
assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration v2", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
assertEquals(0, concept.getOrder());
TermValueSetConcept concept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration v2", 2);
assertTermConceptContainsDesignation(concept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk - expiratie");
assertTermConceptContainsDesignation(concept, "sv", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systoliskt blodtryck - utgång");
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter v2", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(1, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "11378-7", "Systolic blood pressure at First encounter v2", 0);
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum v2", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(22, concept.getOrder());
TermValueSetConcept otherConcept = assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8491-3", "Systolic blood pressure 1 hour minimum v2", 1);
assertTermConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23);
ourLog.info("Concept:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("2", concept.getSystemVersion());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum v2", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
assertEquals(23, concept.getOrder());
assertTermValueSetContainsConceptAndIsInDeclaredOrder(termValueSet, "http://acme.org", "8492-1", "Systolic blood pressure 8 hour minimum v2", 0);
});
}

View File

@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.search.builder.sql;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.config.HibernateDialectProvider;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder;
@ -25,18 +25,18 @@ public class SearchQueryBuilderMySqlTest {
@Mock
private SqlObjectFactory mySqlObjectFactory;
@Mock
private HibernateDialectProvider myHibernateDialectProvider;
private HibernatePropertiesProvider myHibernatePropertiesProvider;
private final FhirContext myFhirContext = FhirContext.forR4();
@BeforeEach
public void beforeInitMocks() {
MockitoAnnotations.initMocks(this);
when(myHibernateDialectProvider.getDialect()).thenReturn(new org.hibernate.dialect.MySQL57Dialect());
when(myHibernatePropertiesProvider.getDialect()).thenReturn(new org.hibernate.dialect.MySQL57Dialect());
}
private SearchQueryBuilder createSearchQueryBuilder() {
return new SearchQueryBuilder(myFhirContext, new ModelConfig(), new PartitionSettings(), RequestPartitionId.allPartitions(), "Patient", mySqlObjectFactory, myHibernateDialectProvider, false);
return new SearchQueryBuilder(myFhirContext, new ModelConfig(), new PartitionSettings(), RequestPartitionId.allPartitions(), "Patient", mySqlObjectFactory, myHibernatePropertiesProvider, false);
}
@Test

View File

@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.search.builder.sql;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.config.HibernateDialectProvider;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
@ -52,7 +52,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeSqlServer2005_NoSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new SQLServer2005Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -78,7 +78,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeSqlServer2005_WithSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new SQLServer2005Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -109,7 +109,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeSqlServer2012_NoSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new SQLServer2012Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -135,7 +135,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeSqlServer2012_WithSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new SQLServer2012Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -165,7 +165,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangePostgreSQL95_NoSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new PostgreSQL95Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -191,7 +191,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangePostgreSQL95_WithSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new PostgreSQL95Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -218,7 +218,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeOracle12c_NoSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new Oracle12cDialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -244,7 +244,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeOracle12c_WithSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new Oracle12cDialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -268,11 +268,10 @@ public class SearchQueryBuilderTest {
}
@Test
public void testRangeMySQL8_NoSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new MySQL8Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -298,7 +297,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeMySQL8_WithSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new MySQL8Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -329,7 +328,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeMariaDB103_NoSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new MariaDB103Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -355,7 +354,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeMariaDB103_WithSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new MariaDB103Dialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -386,7 +385,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeDerbyTenSeven_NoSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new DerbyTenSevenDialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));
@ -412,7 +411,7 @@ public class SearchQueryBuilderTest {
@Test
public void testRangeDerbyTenSeven_WithSort() {
HibernateDialectProvider dialectProvider = new HibernateDialectProvider();
HibernatePropertiesProvider dialectProvider = new HibernatePropertiesProvider();
dialectProvider.setDialectForUnitTest(new DerbyTenSevenDialect());
SearchQueryBuilder builder = new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, "Patient", mySqlBuilderFactory, dialectProvider, false);
builder.addResourceIdsPredicate(Lists.newArrayList(500L, 501L));

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