Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
jamesagnew 2020-06-02 07:47:06 -04:00
commit 201c3a6187
78 changed files with 10780 additions and 563 deletions

View File

@ -159,6 +159,8 @@
<ignoredResource>javac.bat</ignoredResource>
<ignoredResource>about.html</ignoredResource>
<ignoredResource>changelog.xml</ignoredResource>
<ignoredResource>.*/favicon.ico$</ignoredResource>
<ignoredResource>Log4j-charsets.properties</ignoredResource>
</ignoredResourcePatterns>
</configuration>
</plugin>

View File

@ -1776,7 +1776,7 @@ public enum Pointcut {
/**
* <b>Performance Tracing Hook:</b>
* This hook is invoked when a search has failed for any reason. When this pointcut
* This hook is invoked when a search has completed. When this pointcut
* is invoked, a pass in the Search Coordinator has completed successfully, but
* not all possible resources have been loaded yet so a future paging request
* may trigger a new task that will load further resources.
@ -1814,6 +1814,44 @@ public enum Pointcut {
"ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails"
),
/**
* <b>Performance Tracing Hook:</b>
* This hook is invoked when a query involving an external index (e.g. Elasticsearch) has completed. When this pointcut
* is invoked, an initial list of resource IDs has been generated which will be used as part of a subsequent database query.
* <p>
* Note that this is a performance tracing hook. Use with caution in production
* systems, since calling it may (or may not) carry a cost.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. Note that the bean
* properties are not all guaranteed to be populated, depending on how early during processing the
* exception occurred.
* </li>
* <li>
* ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
* only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
* </li>
* <li>
* ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails - Contains details about the search being
* performed. Hooks should not modify this object.
* </li>
* </ul>
* <p>
* Hooks should return <code>void</code>.
* </p>
*/
JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE(void.class,
"ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails",
"ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails"
),
/**
* <b>Performance Tracing Hook:</b>
* Invoked when the storage engine is about to reuse the results of

View File

@ -108,7 +108,7 @@ public class RunServerCommand extends BaseCommand {
ContextHolder.setDisableReferentialIntegrity(true);
}
ContextHolder.setDatabaseUrl(theCommandLine.getOptionValue("u"));
ContextHolder.setDatabaseUrl(theCommandLine.getOptionValue("u"));
String reuseSearchResults = theCommandLine.getOptionValue(OPTION_REUSE_SEARCH_RESULTS_MILLIS);
if (reuseSearchResults != null) {

View File

@ -179,10 +179,10 @@
</overlays>
<webXml>src/main/webapp/WEB-INF/web.xml</webXml>
<attachClasses>true</attachClasses>
<excludes>
<packagingExcludes>
WEB-INF/lib/Saxon-HE-*,
WEB-INF/lib/hapi-*
</excludes>
</packagingExcludes>
</configuration>
</plugin>

View File

@ -20,18 +20,28 @@ package ca.uhn.fhir.jpa.demo;
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
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.elastic.ElasticsearchHibernatePropertiesBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus;
import org.hibernate.search.elasticsearch.cfg.IndexSchemaManagementStrategy;
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 javax.annotation.PreDestroy;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -100,7 +110,55 @@ public class CommonConfig {
extraProperties.put("hibernate.search.autoregister_listeners", "false");
}
return extraProperties;
return configureElasticearch(extraProperties);
}
private Properties configureElasticearch(Properties theExtraProperties) {
String elasticsearchHost = "localhost";
String elasticsearchUserId = "";
String elasticsearchPassword = "";
int elasticsearchPort = embeddedElasticSearch().getHttpPort();
new ElasticsearchHibernatePropertiesBuilder()
.setDebugRefreshAfterWrite(true)
.setDebugPrettyPrintJsonLog(true)
.setIndexSchemaManagementStrategy(IndexSchemaManagementStrategy.CREATE)
.setIndexManagementWaitTimeoutMillis(10000)
.setRequiredIndexStatus(ElasticsearchIndexStatus.YELLOW)
.setRestUrl("http://" + elasticsearchHost + ":" + elasticsearchPort)
.setUsername(elasticsearchUserId)
.setPassword(elasticsearchPassword)
.apply(theExtraProperties);
return theExtraProperties;
}
@Bean
public EmbeddedElastic embeddedElasticSearch() {
String ELASTIC_VERSION = "6.5.4";
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;
}
@PreDestroy
public void stop() {
embeddedElasticSearch().stop();
}
@Bean

View File

@ -0,0 +1,4 @@
---
type: add
issue: 1666
title: "The email sender used by email subscriptions can now be configured with TLS parameters."

View File

@ -0,0 +1,4 @@
---
type: add
issue: 1867
title: "Initial implementation of lastn operation that uses an Elasticsearch v6 server to index observations."

View File

@ -0,0 +1,7 @@
---
type: add
issue: 1871
title: "In the JPA server, when indexing Date SearchParameters where the value being indexed is a
FHIR Period that is missing either a lower bound or an upper bound, a default value representing an
extreme 'beginning of time' or 'end of time' is now used. This allows range searches to return more
accurate results."

View File

@ -522,3 +522,72 @@ The following columns are common to **all HFJ_SPIDX_xxx tables**.
</tbody>
</table>
# HFJ_SPIDX_DATE: Date Search Parameters
For any FHIR Search Parameter of type *date* that generates a database index, a row in the *HFJ_SPIDX_DATE* table will be created.
## Columns
The following columns are common to **all HFJ_SPIDX_xxx tables**.
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Relationships</th>
<th>Datatype</th>
<th>Nullable</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>SP_VALUE_LOW</td>
<td></td>
<td>Timestamp</td>
<td>Nullable</td>
<td>
This is the lower bound of the date in question.
<ul>
<li>For a point in time date to millisecond precision (such as an Instant with a value of <code>2020-05-26T15:00:00.000</code>) this represents the exact value.</li>
<li>For an instant value with lower precision, this represents the start of the possible range denoted by the value. For example, for a value of <code>2020-05-26</code> this represents <code>2020-05-26T00:00:00.000</code>.</li>
<li>For a Period with a lower (start) value present, this column contains that value.</li>
<li>For a Period with no lower (start) value present, this column contains a timestamp representing the "start of time".</li>
</ul>
</td>
</tr>
<tr>
<td>SP_VALUE_HIGH</td>
<td></td>
<td>Timestamp</td>
<td>Nullable</td>
<td>
This is the upper bound of the date in question.
<ul>
<li>For a point in time date to millisecond precision (such as an Instant with a value of <code>2020-05-26T15:00:00.000</code>) this represents the exact value.</li>
<li>For an instant value with lower precision, this represents the start of the possible range denoted by the value. For example, for a value of <code>2020-05-26</code> this represents <code>2020-05-26T23:59:59.999</code>.</li>
<li>For a Period with an upper (end) value present, this column contains that value.</li>
<li>For a Period with no upper (end) value present, this column contains a timestamp representing the "end of time".</li>
</ul>
</td>
</tr>
<tr>
<td>SP_VALUE_LOW_DATE_ORDINAL</td>
<td></td>
<td>Integer</td>
<td>Nullable</td>
<td>
This column contains the same Timestamp as <code>SP_VALUE_LOW</code>, but truncated to Date precision and formatted as an integer in the format "YYYYMMDD".
</td>
</tr>
<tr>
<td>SP_VALUE_HIGH_DATE_ORDINAL</td>
<td></td>
<td>Integer</td>
<td>Nullable</td>
<td>
This column contains the same Timestamp as <code>SP_VALUE_HIGH</code>, but truncated to Date precision and formatted as an integer in the format "YYYYMMDD".
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,144 @@
<?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-fhir</artifactId>
<version>5.1.0-SNAPSHOT</version>
<relativePath>../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

@ -204,6 +204,9 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse);
/**
* Search for IDs for processing a match URLs, etc.
*/
Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest);
/**

View File

@ -0,0 +1,43 @@
package ca.uhn.fhir.jpa.api.dao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.servlet.http.HttpServletResponse;
/*
* #%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%
*/
public interface IFhirResourceDaoObservation<T extends IBaseResource> extends IFhirResourceDao<T> {
/**
* Returns a BundleProvider which can be used to implement the $lastn operation.
* @param paramMap Parameters supported include Observation.subject, Observation.patient, Observation.code,
* Observation.category, and max (the maximum number of Observations to return per specified subjects/patients,
* codes, and/or categories.
* @param theRequestDetails
* @param theServletResponse
* @return
*/
IBundleProvider observationsLastN(SearchParameterMap paramMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse);
}

View File

@ -145,6 +145,13 @@
<artifactId>hapi-fhir-validation-resources-r5</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>
</dependency>
<dependency>
<groupId>net.ttddyy</groupId>
@ -607,6 +614,28 @@
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<forkCount>1</forkCount>
<reuseForks>false</reuseForks>
<runOrder>alphabetical</runOrder>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>de.jpdigital</groupId>
<artifactId>hibernate54-ddl-maven-plugin</artifactId>

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport;
import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl;
import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl;
import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
@ -96,4 +97,11 @@ public abstract class BaseConfigDstu3Plus extends BaseConfig {
@Bean
public abstract ITermReadSvc terminologyService();
@Bean
public ObservationLastNIndexPersistSvc baseObservationLastNIndexpersistSvc() {
return new ObservationLastNIndexPersistSvc();
}
}

View File

@ -1221,9 +1221,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
@Override
public ResourceTable updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion,
IBasePersistedResource theEntity2, IIdType theResourceId, IBaseResource theOldResource, TransactionDetails theTransactionDetails) {
IBasePersistedResource theEntity, IIdType theResourceId, IBaseResource theOldResource, TransactionDetails theTransactionDetails) {
ResourceTable entity = (ResourceTable) theEntity2;
ResourceTable entity = (ResourceTable) theEntity;
// We'll update the resource ID with the correct version later but for
// now at least set it to something useful for the interceptors

View File

@ -0,0 +1,128 @@
package ca.uhn.fhir.jpa.dao;
/*
* #%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.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.*;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.ReferenceParam;
import org.hl7.fhir.instance.model.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TreeMap;
public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoObservation<T> {
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
@Autowired
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
protected ResourceTable updateObservationEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity,
Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion,
theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry);
if (!retVal.isUnchangedInCurrentOperation()) {
if (retVal.getDeleted() == null) {
// Update indexes here for LastN operation.
myObservationLastNIndexPersistSvc.indexObservation(theResource);
} else {
myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
}
}
return retVal;
}
protected void updateSearchParamsForLastn(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
if (!isPagingProviderDatabaseBacked(theRequestDetails)) {
theSearchParameterMap.setLoadSynchronous(true);
}
theSearchParameterMap.setLastN(true);
SortSpec effectiveDtm = new SortSpec(getEffectiveParamName()).setOrder(SortOrderEnum.DESC);
SortSpec observationCode = new SortSpec(getCodeParamName()).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm);
if(theSearchParameterMap.containsKey(getSubjectParamName()) || theSearchParameterMap.containsKey(getPatientParamName())) {
fixSubjectParamsOrderForLastn(theSearchParameterMap, theRequestDetails);
theSearchParameterMap.setSort(new SortSpec(getSubjectParamName()).setOrder(SortOrderEnum.ASC).setChain(observationCode));
} else {
theSearchParameterMap.setSort(observationCode);
}
}
private void fixSubjectParamsOrderForLastn(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
// Need to ensure that the patient/subject parameters are sorted in the SearchParameterMap to ensure correct ordering of
// the output. The reason for this is that observations are indexed by patient/subject forced ID, but then ordered in the
// final result set by subject/patient resource PID.
TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>();
if(theSearchParameterMap.containsKey(getSubjectParamName())) {
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, getResourceName());
List<List<IQueryParameterType>> patientParams = new ArrayList<>();
if (theSearchParameterMap.get(getPatientParamName()) != null) {
patientParams.addAll(theSearchParameterMap.get(getPatientParamName()));
}
if (theSearchParameterMap.get(getSubjectParamName()) != null) {
patientParams.addAll(theSearchParameterMap.get(getSubjectParamName()));
}
for (List<? extends IQueryParameterType> nextPatientList : patientParams) {
for (IQueryParameterType nextOr : nextPatientList) {
if (nextOr instanceof ReferenceParam) {
ReferenceParam ref = (ReferenceParam) nextOr;
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, ref.getResourceType(), ref.getIdPart());
orderedSubjectReferenceMap.put(pid.getIdAsLong(), nextOr);
} else {
throw new IllegalArgumentException("Invalid token type (expecting ReferenceParam): " + nextOr.getClass());
}
}
}
theSearchParameterMap.remove(getSubjectParamName());
theSearchParameterMap.remove(getPatientParamName());
for (Long subjectPid : orderedSubjectReferenceMap.keySet()) {
theSearchParameterMap.add(getSubjectParamName(), orderedSubjectReferenceMap.get(subjectPid));
}
}
}
abstract protected String getEffectiveParamName();
abstract protected String getCodeParamName();
abstract protected String getSubjectParamName();
abstract protected String getPatientParamName();
}

View File

@ -0,0 +1,228 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodingSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedSearchParamLastNDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCategoryCodeableConceptEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCategoryCodingEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodingEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
import org.hl7.fhir.instance.model.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.util.*;
@Transactional(propagation = Propagation.REQUIRED)
public class ObservationLastNIndexPersistSvc {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
IObservationIndexedSearchParamLastNDao myResourceIndexedObservationLastNDao;
@Autowired
IObservationIndexedCodeCodingSearchParamDao myObservationIndexedCodeCodingSearchParamDao;
@Autowired
public ISearchParamExtractor mySearchParameterExtractor;
public void indexObservation(IBaseResource theResource) {
List<IBase> subjectReferenceElement = mySearchParameterExtractor.extractValues("Observation.subject", theResource);
String subjectId = subjectReferenceElement.stream()
.map(refElement -> mySearchParameterExtractor.extractReferenceLinkFromResource(refElement, "Observation.subject"))
.filter(Objects::nonNull)
.map(PathAndRef::getRef)
.filter(Objects::nonNull)
.map(subjectRef -> subjectRef.getReferenceElement().getValue())
.filter(Objects::nonNull)
.findFirst().orElse(null);
Date effectiveDtm = null;
List<IBase> effectiveDateElement = mySearchParameterExtractor.extractValues("Observation.effective", theResource);
if (effectiveDateElement.size() > 0) {
effectiveDtm = mySearchParameterExtractor.extractDateFromResource(effectiveDateElement.get(0), "Observation.effective");
}
List<IBase> observationCodeCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.code", theResource);
// Only index for lastn if Observation has a code
if (observationCodeCodeableConcepts.size() == 0) {
return;
}
List<IBase> observationCategoryCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.category", theResource);
String resourcePID = theResource.getIdElement().getIdPart();
createOrUpdateIndexedObservation(resourcePID, effectiveDtm, subjectId, observationCodeCodeableConcepts, observationCategoryCodeableConcepts);
}
private void createOrUpdateIndexedObservation(String resourcePID, Date theEffectiveDtm, String theSubjectId,
List<IBase> theObservationCodeCodeableConcepts,
List<IBase> theObservationCategoryCodeableConcepts) {
// Determine if an index already exists for Observation:
boolean observationIndexUpdate = false;
ObservationIndexedSearchParamLastNEntity indexedObservation = null;
if (resourcePID != null) {
indexedObservation = myResourceIndexedObservationLastNDao.findByIdentifier(resourcePID);
}
if (indexedObservation == null) {
indexedObservation = new ObservationIndexedSearchParamLastNEntity();
} else {
observationIndexUpdate = true;
}
indexedObservation.setEffectiveDtm(theEffectiveDtm);
indexedObservation.setIdentifier(resourcePID);
indexedObservation.setSubject(theSubjectId);
addCodeToObservationIndex(theObservationCodeCodeableConcepts, indexedObservation);
addCategoriesToObservationIndex(theObservationCategoryCodeableConcepts, indexedObservation);
if (observationIndexUpdate) {
myEntityManager.merge(indexedObservation);
} else {
myEntityManager.persist(indexedObservation);
}
}
private void addCodeToObservationIndex(List<IBase> theObservationCodeCodeableConcepts,
ObservationIndexedSearchParamLastNEntity theIndexedObservation) {
// Determine if a Normalized ID was created previously for Observation Code
Optional<String> existingObservationCodeNormalizedId = getCodeCodeableConceptIdIfExists(theObservationCodeCodeableConcepts.get(0));
// Create/update normalized Observation Code index record
ObservationIndexedCodeCodeableConceptEntity codeableConceptField =
getCodeCodeableConcept(theObservationCodeCodeableConcepts.get(0),
existingObservationCodeNormalizedId.orElse(UUID.randomUUID().toString()));
if (existingObservationCodeNormalizedId.isPresent()) {
myEntityManager.merge(codeableConceptField);
} else {
myEntityManager.persist(codeableConceptField);
}
theIndexedObservation.setObservationCode(codeableConceptField);
theIndexedObservation.setCodeNormalizedId(codeableConceptField.getCodeableConceptId());
}
private void addCategoriesToObservationIndex(List<IBase> observationCategoryCodeableConcepts,
ObservationIndexedSearchParamLastNEntity indexedObservation) {
// Build CodeableConcept entities for Observation.Category
Set<ObservationIndexedCategoryCodeableConceptEntity> categoryCodeableConceptEntities = new HashSet<>();
for (IBase categoryCodeableConcept : observationCategoryCodeableConcepts) {
// Build CodeableConcept entities for each category CodeableConcept
categoryCodeableConceptEntities.add(getCategoryCodeableConceptEntities(categoryCodeableConcept));
}
indexedObservation.setCategoryCodeableConcepts(categoryCodeableConceptEntities);
}
private ObservationIndexedCategoryCodeableConceptEntity getCategoryCodeableConceptEntities(IBase theValue) {
String text = mySearchParameterExtractor.getDisplayTextFromCodeableConcept(theValue);
ObservationIndexedCategoryCodeableConceptEntity categoryCodeableConcept = new ObservationIndexedCategoryCodeableConceptEntity(text);
List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue);
Set<ObservationIndexedCategoryCodingEntity> categoryCodingEntities = new HashSet<>();
for (IBase nextCoding : codings) {
categoryCodingEntities.add(getCategoryCoding(nextCoding));
}
categoryCodeableConcept.setObservationIndexedCategoryCodingEntitySet(categoryCodingEntities);
return categoryCodeableConcept;
}
private ObservationIndexedCodeCodeableConceptEntity getCodeCodeableConcept(IBase theValue, String observationCodeNormalizedId) {
String text = mySearchParameterExtractor.getDisplayTextFromCodeableConcept(theValue);
ObservationIndexedCodeCodeableConceptEntity codeCodeableConcept = new ObservationIndexedCodeCodeableConceptEntity(text, observationCodeNormalizedId);
List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue);
for (IBase nextCoding : codings) {
codeCodeableConcept.addCoding(getCodeCoding(nextCoding, observationCodeNormalizedId));
}
return codeCodeableConcept;
}
private Optional<String> getCodeCodeableConceptIdIfExists(IBase theValue) {
List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue);
String codeCodeableConceptId = null;
Optional<String> codeCodeableConceptIdOptional = Optional.empty();
for (IBase nextCoding : codings) {
ResourceIndexedSearchParamToken param = mySearchParameterExtractor.createSearchParamForCoding("Observation",
new RuntimeSearchParam(null, null, "code", null, null, null,
null, null, null, null),
nextCoding);
if (param != null) {
String system = param.getSystem();
String code = param.getValue();
String text = mySearchParameterExtractor.getDisplayTextForCoding(nextCoding);
if (code != null && system != null) {
codeCodeableConceptIdOptional = Optional.ofNullable(myObservationIndexedCodeCodingSearchParamDao.findByCodeAndSystem(code, system));
} else {
codeCodeableConceptIdOptional = Optional.ofNullable(myObservationIndexedCodeCodingSearchParamDao.findByDisplay(text));
}
if (codeCodeableConceptIdOptional.isPresent()) {
break;
}
}
}
return codeCodeableConceptIdOptional;
}
private ObservationIndexedCategoryCodingEntity getCategoryCoding(IBase theValue) {
ResourceIndexedSearchParamToken param = mySearchParameterExtractor.createSearchParamForCoding("Observation",
new RuntimeSearchParam(null, null, "category", null, null, null, null, null, null, null),
theValue);
ObservationIndexedCategoryCodingEntity observationIndexedCategoryCodingEntity = null;
if (param != null) {
String system = param.getSystem();
String code = param.getValue();
String text = mySearchParameterExtractor.getDisplayTextForCoding(theValue);
observationIndexedCategoryCodingEntity = new ObservationIndexedCategoryCodingEntity(system, code, text);
}
return observationIndexedCategoryCodingEntity;
}
private ObservationIndexedCodeCodingEntity getCodeCoding(IBase theValue, String observationCodeNormalizedId) {
ResourceIndexedSearchParamToken param = mySearchParameterExtractor.createSearchParamForCoding("Observation",
new RuntimeSearchParam(null, null, "code", null, null, null, null, null, null, null),
theValue);
ObservationIndexedCodeCodingEntity observationIndexedCodeCodingEntity = null;
if (param != null) {
String system = param.getSystem();
String code = param.getValue();
String text = mySearchParameterExtractor.getDisplayTextForCoding(theValue);
observationIndexedCodeCodingEntity = new ObservationIndexedCodeCodingEntity(system, code, text, observationCodeNormalizedId);
}
return observationIndexedCodeCodingEntity;
}
public void deleteObservationIndex(IBasePersistedResource theEntity) {
ObservationIndexedSearchParamLastNEntity deletedObservationLastNEntity = myResourceIndexedObservationLastNDao.findByIdentifier(theEntity.getIdDt().getIdPart());
if (deletedObservationLastNEntity != null) {
myEntityManager.remove(deletedObservationLastNEntity);
}
}
}

View File

@ -48,10 +48,12 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.search.lastn.IElasticsearchSvc;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.util.Dstu3DistanceHelper;
import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper;
import ca.uhn.fhir.jpa.util.BaseIterator;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
@ -79,7 +81,6 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.Validate;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
@ -129,6 +130,8 @@ public class SearchBuilder implements ISearchBuilder {
*/
// NB: keep public
public static final int MAXIMUM_PAGE_SIZE = 800;
public static final int MAXIMUM_PAGE_SIZE_FOR_TESTING = 50;
public static boolean myUseMaxPageSize50ForTest = false;
private static final List<ResourcePersistentId> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>());
private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class);
@ -153,6 +156,8 @@ public class SearchBuilder implements ISearchBuilder {
private IdHelperService myIdHelperService;
@Autowired(required = false)
private IFulltextSearchSvc myFulltextSearchSvc;
@Autowired(required = false)
private IElasticsearchSvc myIElasticsearchSvc;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
@ -178,6 +183,18 @@ public class SearchBuilder implements ISearchBuilder {
myResourceType = theResourceType;
}
public static int getMaximumPageSize() {
if (myUseMaxPageSize50ForTest) {
return MAXIMUM_PAGE_SIZE_FOR_TESTING;
} else {
return MAXIMUM_PAGE_SIZE;
}
}
public static void setMaxPageSize50ForTest(boolean theIsTest) {
myUseMaxPageSize50ForTest = theIsTest;
}
@Override
public void setMaxResultsToFetch(Integer theMaxResultsToFetch) {
myMaxResultsToFetch = theMaxResultsToFetch;
@ -206,6 +223,10 @@ public class SearchBuilder implements ISearchBuilder {
// Handle each parameter
for (Map.Entry<String, List<List<IQueryParameterType>>> nextParamEntry : myParams.entrySet()) {
String nextParamName = nextParamEntry.getKey();
if (myParams.isLastN() && LastNParameterHelper.isLastNParameter(nextParamName, myContext)) {
// Skip parameters for Subject, Patient, Code and Category for LastN as these will be filtered by Elasticsearch
continue;
}
List<List<IQueryParameterType>> andOrParams = nextParamEntry.getValue();
searchForIdsWithAndOr(myResourceName, nextParamName, andOrParams, theRequest);
}
@ -227,8 +248,8 @@ public class SearchBuilder implements ISearchBuilder {
init(theParams, theSearchUuid, theRequestPartitionId);
TypedQuery<Long> query = createQuery(null, null, true, theRequest);
return new CountQueryIterator(query);
List<TypedQuery<Long>> queries = createQuery(null, null, true, theRequest, null);
return new CountQueryIterator(queries.get(0));
}
/**
@ -261,8 +282,81 @@ public class SearchBuilder implements ISearchBuilder {
myRequestPartitionId = theRequestPartitionId;
}
private List<TypedQuery<Long>> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest,
SearchRuntimeDetails theSearchRuntimeDetails) {
private TypedQuery<Long> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) {
List<ResourcePersistentId> pids = new ArrayList<>();
/*
* Fulltext or lastn search
*/
if (myParams.containsKey(Constants.PARAM_CONTENT) || myParams.containsKey(Constants.PARAM_TEXT) || myParams.isLastN()) {
if (myParams.containsKey(Constants.PARAM_CONTENT) || myParams.containsKey(Constants.PARAM_TEXT)) {
if (myFulltextSearchSvc == null) {
if (myParams.containsKey(Constants.PARAM_TEXT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_TEXT);
} else if (myParams.containsKey(Constants.PARAM_CONTENT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT);
}
}
if (myParams.getEverythingMode() != null) {
pids = myFulltextSearchSvc.everything(myResourceName, myParams, theRequest);
} else {
pids = myFulltextSearchSvc.search(myResourceName, myParams);
}
} else if (myParams.isLastN()) {
if (myIElasticsearchSvc == null) {
if (myParams.isLastN()) {
throw new InvalidRequestException("LastN operation is not enabled on this service, can not process this request");
}
}
if(myParams.getLastNMax() == null) {
throw new InvalidRequestException("Max parameter is required for $lastn operation");
}
List<String> lastnResourceIds = myIElasticsearchSvc.executeLastN(myParams, myContext, theMaximumResults);
for (String lastnResourceId : lastnResourceIds) {
pids.add(myIdHelperService.resolveResourcePersistentIds(myRequestPartitionId, myResourceName, lastnResourceId));
}
}
if (theSearchRuntimeDetails != null) {
theSearchRuntimeDetails.setFoundIndexMatchesCount(pids.size());
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(SearchRuntimeDetails.class, theSearchRuntimeDetails);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE, params);
}
if (pids.isEmpty()) {
// Will never match
pids = Collections.singletonList(new ResourcePersistentId(-1L));
}
}
ArrayList<TypedQuery<Long>> myQueries = new ArrayList<>();
if (!pids.isEmpty()) {
if (theMaximumResults != null && pids.size() > theMaximumResults) {
pids.subList(0,theMaximumResults-1);
}
new QueryChunker<Long>().chunk(ResourcePersistentId.toLongList(pids), t-> doCreateChunkedQueries(t, sort, theCount, theRequest, myQueries));
} else {
myQueries.add(createChunkedQuery(sort,theMaximumResults, theCount, theRequest, null));
}
return myQueries;
}
private void doCreateChunkedQueries(List<Long> thePids, SortSpec sort, boolean theCount, RequestDetails theRequest, ArrayList<TypedQuery<Long>> theQueries) {
if(thePids.size() < getMaximumPageSize()) {
normalizeIdListForLastNInClause(thePids);
}
theQueries.add(createChunkedQuery(sort, thePids.size(), theCount, theRequest, thePids));
}
private TypedQuery<Long> createChunkedQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest, List<Long> thePidList) {
/*
* Sort
*
@ -310,30 +404,9 @@ public class SearchBuilder implements ISearchBuilder {
searchForIdsWithAndOr(myParams, theRequest);
}
/*
* Fulltext search
*/
if (myParams.containsKey(Constants.PARAM_CONTENT) || myParams.containsKey(Constants.PARAM_TEXT)) {
if (myFulltextSearchSvc == null) {
if (myParams.containsKey(Constants.PARAM_TEXT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_TEXT);
} else if (myParams.containsKey(Constants.PARAM_CONTENT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT);
}
}
List<ResourcePersistentId> pids;
if (myParams.getEverythingMode() != null) {
pids = myFulltextSearchSvc.everything(myResourceName, myParams, theRequest);
} else {
pids = myFulltextSearchSvc.search(myResourceName, myParams);
}
if (pids.isEmpty()) {
// Will never match
pids = Collections.singletonList(new ResourcePersistentId(-1L));
}
myQueryStack.addPredicate(myQueryStack.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(pids)));
// Add PID list predicate for full text search and/or lastn operation
if (thePidList != null && thePidList.size() > 0) {
myQueryStack.addPredicate(myQueryStack.get("myId").as(Long.class).in(thePidList));
}
// Last updated
@ -355,6 +428,40 @@ public class SearchBuilder implements ISearchBuilder {
return query;
}
private List<Long> normalizeIdListForLastNInClause(List<Long> lastnResourceIds) {
/*
The following is a workaround to a known issue involving Hibernate. If queries are used with "in" clauses with large and varying
numbers of parameters, this can overwhelm Hibernate's QueryPlanCache and deplete heap space. See the following link for more info:
https://stackoverflow.com/questions/31557076/spring-hibernate-query-plan-cache-memory-usage.
Normalizing the number of parameters in the "in" clause stabilizes the size of the QueryPlanCache, so long as the number of
arguments never exceeds the maximum specified below.
*/
int listSize = lastnResourceIds.size();
if(listSize > 1 && listSize < 10) {
padIdListWithPlaceholders(lastnResourceIds, 10);
} else if (listSize > 10 && listSize < 50) {
padIdListWithPlaceholders(lastnResourceIds, 50);
} else if (listSize > 50 && listSize < 100) {
padIdListWithPlaceholders(lastnResourceIds, 100);
} else if (listSize > 100 && listSize < 200) {
padIdListWithPlaceholders(lastnResourceIds, 200);
} else if (listSize > 200 && listSize < 500) {
padIdListWithPlaceholders(lastnResourceIds, 500);
} else if (listSize > 500 && listSize < 800) {
padIdListWithPlaceholders(lastnResourceIds, 800);
}
return lastnResourceIds;
}
private void padIdListWithPlaceholders(List<Long> theIdList, int preferredListSize) {
while(theIdList.size() < preferredListSize) {
theIdList.add(-1L);
}
}
/**
* @return Returns {@literal true} if any search parameter sorts were found, or false if
* no sorts were found, or only non-search parameters ones (e.g. _id, _lastUpdated)
@ -478,10 +585,17 @@ public class SearchBuilder implements ISearchBuilder {
private void doLoadPids(Collection<ResourcePersistentId> thePids, Collection<ResourcePersistentId> theIncludedPids, List<IBaseResource> theResourceListToPopulate, boolean theForHistoryOperation,
Map<ResourcePersistentId, Integer> thePosition, RequestDetails theRequest) {
Map<ResourcePersistentId, Integer> thePosition) {
List<Long> myLongPersistentIds;
if(thePids.size() < getMaximumPageSize()) {
myLongPersistentIds = normalizeIdListForLastNInClause(ResourcePersistentId.toLongList(thePids));
} else {
myLongPersistentIds = ResourcePersistentId.toLongList(thePids);
}
// -- get the resource from the searchView
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(ResourcePersistentId.toLongList(thePids));
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(myLongPersistentIds);
//-- preload all tags with tag definition if any
Map<ResourcePersistentId, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
@ -581,9 +695,7 @@ public class SearchBuilder implements ISearchBuilder {
}
List<ResourcePersistentId> pids = new ArrayList<>(thePids);
new QueryChunker<ResourcePersistentId>().chunk(pids, t -> {
doLoadPids(t, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position, theDetails);
});
new QueryChunker<ResourcePersistentId>().chunk(pids, t -> doLoadPids(t, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position));
}
@ -627,7 +739,7 @@ public class SearchBuilder implements ISearchBuilder {
if (matchAll) {
String sql;
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids) ";
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, MAXIMUM_PAGE_SIZE);
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, getMaximumPageSize());
for (Collection<ResourcePersistentId> nextPartition : partitions) {
TypedQuery<Long> q = theEntityManager.createQuery(sql, Long.class);
q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition));
@ -680,7 +792,7 @@ public class SearchBuilder implements ISearchBuilder {
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
}
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, MAXIMUM_PAGE_SIZE);
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, getMaximumPageSize());
for (Collection<ResourcePersistentId> nextPartition : partitions) {
TypedQuery<Long> q = theEntityManager.createQuery(sql, Long.class);
q.setParameter("src_path", nextPath);
@ -925,9 +1037,8 @@ public class SearchBuilder implements ISearchBuilder {
private final RequestDetails myRequest;
private Iterator<ResourcePersistentId> myCurrentIterator;
private Set<ResourcePersistentId> myCurrentPids;
private final Set<ResourcePersistentId> myCurrentPids;
private ResourcePersistentId myNext;
private int myPageSize = myDaoConfig.getEverythingIncludesFetchPageSize();
IncludesIterator(Set<ResourcePersistentId> thePidSet, RequestDetails theRequest) {
myCurrentPids = new HashSet<>(thePidSet);
@ -980,11 +1091,13 @@ public class SearchBuilder implements ISearchBuilder {
private ResourcePersistentId myNext;
private Iterator<ResourcePersistentId> myPreResultsIterator;
private ScrollableResultsIterator<Long> myResultsIterator;
private SortSpec mySort;
private final SortSpec mySort;
private boolean myStillNeedToFetchIncludes;
private int mySkipCount = 0;
private int myNonSkipCount = 0;
private List<TypedQuery<Long>> myQueryList = new ArrayList<>();
private QueryIterator(SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) {
mySearchRuntimeDetails = theSearchRuntimeDetails;
mySort = myParams.getSort();
@ -1035,7 +1148,12 @@ public class SearchBuilder implements ISearchBuilder {
}
if (myNext == null) {
while (myResultsIterator.hasNext()) {
while (myResultsIterator.hasNext() || !myQueryList.isEmpty()) {
// Update iterator with next chunk if necessary.
if (!myResultsIterator.hasNext()) {
retrieveNextIteratorQuery();
}
Long nextLong = myResultsIterator.next();
if (myHavePerfTraceFoundIdHook) {
HookParams params = new HookParams()
@ -1134,19 +1252,33 @@ public class SearchBuilder implements ISearchBuilder {
}
private void initializeIteratorQuery(Integer theMaxResultsToFetch) {
final TypedQuery<Long> query = createQuery(mySort, theMaxResultsToFetch, false, myRequest);
if (myQueryList.isEmpty()) {
// Capture times for Lucene/Elasticsearch queries as well
mySearchRuntimeDetails.setQueryStopwatch(new StopWatch());
myQueryList = createQuery(mySort, theMaxResultsToFetch, false, myRequest, mySearchRuntimeDetails);
}
mySearchRuntimeDetails.setQueryStopwatch(new StopWatch());
Query<Long> hibernateQuery = (Query<Long>) query;
hibernateQuery.setFetchSize(myFetchSize);
ScrollableResults scroll = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY);
myResultsIterator = new ScrollableResultsIterator<>(scroll);
retrieveNextIteratorQuery();
mySkipCount = 0;
myNonSkipCount = 0;
}
private void retrieveNextIteratorQuery() {
if (myQueryList != null && myQueryList.size() > 0) {
final TypedQuery<Long> query = myQueryList.remove(0);
Query<Long> hibernateQuery = (Query<Long>) (query);
hibernateQuery.setFetchSize(myFetchSize);
ScrollableResults scroll = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY);
myResultsIterator = new ScrollableResultsIterator<>(scroll);
} else {
myResultsIterator = null;
}
}
@Override
public boolean hasNext() {
if (myNext == null) {

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface IObservationIndexedCodeCodeableConceptSearchParamDao extends JpaRepository<ObservationIndexedCodeCodeableConceptEntity, Long> {
@Query("" +
"SELECT t FROM ObservationIndexedCodeCodeableConceptEntity t " +
"WHERE t.myCodeableConceptId = :codeableConceptId" +
"")
ObservationIndexedCodeCodeableConceptEntity findByCodeableConceptId(@Param("codeableConceptId") String theCodeableConceptId);
}

View File

@ -0,0 +1,26 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodingEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface IObservationIndexedCodeCodingSearchParamDao extends JpaRepository<ObservationIndexedCodeCodingEntity, Long> {
@Query("" +
"SELECT t.myCodeableConceptId FROM ObservationIndexedCodeCodingEntity t " +
"WHERE t.myCode = :code " +
"AND t.mySystem = :system " +
"")
String findByCodeAndSystem(@Param("code") String theCode, @Param("system") String theSystem);
@Query("" +
"SELECT t.myCodeableConceptId FROM ObservationIndexedCodeCodingEntity t " +
"WHERE t.myDisplay = :display" +
"")
String findByDisplay(@Param("display") String theDisplay);
}

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface IObservationIndexedSearchParamLastNDao extends JpaRepository<ObservationIndexedSearchParamLastNEntity, Long>{
@Query("" +
"SELECT t FROM ObservationIndexedSearchParamLastNEntity t " +
"WHERE t.myIdentifier = :identifier" +
"")
ObservationIndexedSearchParamLastNEntity findByIdentifier(@Param("identifier") String theIdentifier);
}

View File

@ -0,0 +1,81 @@
package ca.uhn.fhir.jpa.dao.dstu3;
/*
* #%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.dao.BaseHapiFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.dstu3.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class FhirResourceDaoObservationDstu3 extends BaseHapiFhirResourceDaoObservation<Observation> {
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails);
}
@Override
protected String getEffectiveParamName() {
return Observation.SP_DATE;
}
@Override
protected String getCodeParamName() {
return Observation.SP_CODE;
}
@Override
protected String getSubjectParamName() {
return Observation.SP_SUBJECT;
}
@Override
protected String getPatientParamName() {
return Observation.SP_PATIENT;
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -35,6 +35,7 @@ import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@Service
@ -67,13 +68,20 @@ public class DaoSearchParamSynchronizer {
}
private <T extends BaseResourceIndex> void synchronize(ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection<T> theNewParams, Collection<T> theExistingParams) {
for (T next : theNewParams) {
Collection<T> newParams = theNewParams;
for (T next : newParams) {
next.setPartitionId(theEntity.getPartitionId());
next.calculateHashes();
}
List<T> paramsToRemove = subtract(theExistingParams, theNewParams);
List<T> paramsToAdd = subtract(theNewParams, theExistingParams);
/*
* HashCodes may have changed as a result of setting the partition ID, so
* create a new set that will reflect the new hashcodes
*/
newParams = new HashSet<>(newParams);
List<T> paramsToRemove = subtract(theExistingParams, newParams);
List<T> paramsToAdd = subtract(newParams, theExistingParams);
tryToReuseIndexEntities(paramsToRemove, paramsToAdd);
for (T next : paramsToRemove) {
@ -127,8 +135,12 @@ public class DaoSearchParamSynchronizer {
return new ArrayList<>();
}
ArrayList<T> retVal = new ArrayList<>(theSubtractFrom);
retVal.removeAll(theToSubtract);
ArrayList<T> retVal = new ArrayList<>();
for (T next : theSubtractFrom) {
if (!theToSubtract.contains(next)) {
retVal.add(next);
}
}
return retVal;
}
}

View File

@ -0,0 +1,88 @@
package ca.uhn.fhir.jpa.dao.r4;
/*
* #%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.dao.BaseHapiFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDaoObservation<Observation> {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails);
}
@Override
protected String getEffectiveParamName() {
return Observation.SP_DATE;
}
@Override
protected String getCodeParamName() {
return Observation.SP_CODE;
}
@Override
protected String getSubjectParamName() {
return Observation.SP_SUBJECT;
}
@Override
protected String getPatientParamName() {
return Observation.SP_PATIENT;
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -0,0 +1,81 @@
package ca.uhn.fhir.jpa.dao.r5;
/*
* #%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.dao.BaseHapiFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class FhirResourceDaoObservationR5 extends BaseHapiFhirResourceDaoObservation<Observation> {
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails);
}
@Override
protected String getEffectiveParamName() {
return Observation.SP_DATE;
}
@Override
protected String getCodeParamName() {
return Observation.SP_CODE;
}
@Override
protected String getSubjectParamName() {
return Observation.SP_SUBJECT;
}
@Override
protected String getPatientParamName() {
return Observation.SP_PATIENT;
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -79,6 +79,11 @@ public class PerformanceTracingLoggingInterceptor {
log("SqlQuery {} failed in {} - Found {} matches", theOutcome.getSearchUuid(), theOutcome.getQueryStopwatch(), theOutcome.getFoundMatchesCount());
}
@Hook(value = Pointcut.JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE)
public void indexSearchQueryComplete(SearchRuntimeDetails theOutcome) {
log("Index query for {} completed in {} - Found {} matches", theOutcome.getSearchUuid(), theOutcome.getQueryStopwatch(), theOutcome.getFoundIndexMatchesCount());
}
@Hook(value = Pointcut.JPA_PERFTRACE_INFO)
public void info(StorageProcessingMessage theMessage) {
log("[INFO] " + theMessage);

View File

@ -0,0 +1,98 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.UnsignedIntType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
/*
* #%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%
*/
public class BaseJpaResourceProviderObservationDstu3 extends JpaResourceProviderDstu3<Observation> {
/**
* Observation/$lastn
*/
@Operation(name = JpaConstants.OPERATION_LASTN, idempotent = true, bundleType = BundleTypeEnum.SEARCHSET)
public IBundleProvider observationLastN(
javax.servlet.http.HttpServletRequest theServletRequest,
javax.servlet.http.HttpServletResponse theServletResponse,
ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails,
@Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
@OperationParam(name = Constants.PARAM_COUNT)
UnsignedIntType theCount,
@Description(shortDefinition="The classification of the type of observation")
@OperationParam(name="category")
TokenAndListParam theCategory,
@Description(shortDefinition="The code of the observation type")
@OperationParam(name="code")
TokenAndListParam theCode,
@Description(shortDefinition="The subject that the observation is about (if patient)")
@OperationParam(name="patient")
ReferenceAndListParam thePatient,
@Description(shortDefinition="The subject that the observation is about")
@OperationParam(name="subject" )
ReferenceAndListParam theSubject,
@Description(shortDefinition="The maximum number of observations to return for each observation code")
@OperationParam(name = "max", typeName = "integer", min = 0, max = 1)
IPrimitiveType<Integer> theMax
) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(Observation.SP_CATEGORY, theCategory);
paramMap.add(Observation.SP_CODE, theCode);
if (thePatient != null) {
paramMap.add("patient", thePatient);
}
if (theSubject != null) {
paramMap.add("subject", theSubject);
}
paramMap.setLastNMax(theMax.getValue());
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -0,0 +1,98 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.*;
/*
* #%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%
*/
public class BaseJpaResourceProviderObservationR4 extends JpaResourceProviderR4<Observation> {
/**
* Observation/$lastn
*/
@Operation(name = JpaConstants.OPERATION_LASTN, idempotent = true, bundleType = BundleTypeEnum.SEARCHSET)
public IBundleProvider observationLastN(
javax.servlet.http.HttpServletRequest theServletRequest,
javax.servlet.http.HttpServletResponse theServletResponse,
ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails,
@Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
@OperationParam(name = Constants.PARAM_COUNT)
UnsignedIntType theCount,
@Description(shortDefinition="The classification of the type of observation")
@OperationParam(name="category")
TokenAndListParam theCategory,
@Description(shortDefinition="The code of the observation type")
@OperationParam(name="code")
TokenAndListParam theCode,
@Description(shortDefinition="The subject that the observation is about (if patient)")
@OperationParam(name="patient")
ReferenceAndListParam thePatient,
@Description(shortDefinition="The subject that the observation is about")
@OperationParam(name="subject" )
ReferenceAndListParam theSubject,
@Description(shortDefinition="The maximum number of observations to return for each observation code")
@OperationParam(name = "max", typeName = "integer", min = 0, max = 1)
IPrimitiveType<Integer> theMax
) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(Observation.SP_CATEGORY, theCategory);
paramMap.add(Observation.SP_CODE, theCode);
if (thePatient != null) {
paramMap.add(Observation.SP_PATIENT, thePatient);
}
if (theSubject != null) {
paramMap.add(Observation.SP_SUBJECT, theSubject);
}
paramMap.setLastNMax(theMax.getValue());
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -0,0 +1,98 @@
package ca.uhn.fhir.jpa.provider.r5;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.UnsignedIntType;
import org.hl7.fhir.r5.model.Observation;
/*
* #%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%
*/
public class BaseJpaResourceProviderObservationR5 extends JpaResourceProviderR5<Observation> {
/**
* Observation/$lastn
*/
@Operation(name = JpaConstants.OPERATION_LASTN, idempotent = true, bundleType = BundleTypeEnum.SEARCHSET)
public IBundleProvider observationLastN(
javax.servlet.http.HttpServletRequest theServletRequest,
javax.servlet.http.HttpServletResponse theServletResponse,
ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails,
@Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
@OperationParam(name = Constants.PARAM_COUNT)
UnsignedIntType theCount,
@Description(shortDefinition="The classification of the type of observation")
@OperationParam(name="category")
TokenAndListParam theCategory,
@Description(shortDefinition="The code of the observation type")
@OperationParam(name="code")
TokenAndListParam theCode,
@Description(shortDefinition="The subject that the observation is about (if patient)")
@OperationParam(name="patient")
ReferenceAndListParam thePatient,
@Description(shortDefinition="The subject that the observation is about")
@OperationParam(name="subject" )
ReferenceAndListParam theSubject,
@Description(shortDefinition="The maximum number of observations to return for each observation code")
@OperationParam(name = "max", typeName = "integer", min = 0, max = 1)
IPrimitiveType<Integer> theMax
) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(Observation.SP_CATEGORY, theCategory);
paramMap.add(Observation.SP_CODE, theCode);
if (thePatient != null) {
paramMap.add(Observation.SP_PATIENT, thePatient);
}
if (theSubject != null) {
paramMap.add(Observation.SP_SUBJECT, theSubject);
}
paramMap.setLastNMax(theMax.getValue());
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -0,0 +1,33 @@
package ca.uhn.fhir.jpa.search.lastn;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
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;
public class ElasticsearchRestClientFactory {
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))
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider));
Header[] defaultHeaders = new Header[]{new BasicHeader("Content-Type", "application/json")};
clientBuilder.setDefaultHeaders(defaultHeaders);
return new RestHighLevelClient(clientBuilder);
}
}

View File

@ -0,0 +1,546 @@
package ca.uhn.fhir.jpa.search.lastn;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
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.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.QueryBuilders;
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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class ElasticsearchSvcImpl implements IElasticsearchSvc {
public static final String OBSERVATION_INDEX = "observation_index";
public static final String OBSERVATION_CODE_INDEX = "code_index";
public static final String OBSERVATION_DOCUMENT_TYPE = "ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity";
public static final String CODE_DOCUMENT_TYPE = "ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity";
public static final String OBSERVATION_INDEX_SCHEMA_FILE = "ObservationIndexSchema.json";
public static final String OBSERVATION_CODE_INDEX_SCHEMA_FILE = "ObservationCodeIndexSchema.json";
private final RestHighLevelClient myRestHighLevelClient;
private final ObjectMapper objectMapper = new ObjectMapper();
private final String GROUP_BY_SUBJECT = "group_by_subject";
private final String GROUP_BY_SYSTEM = "group_by_system";
private final String GROUP_BY_CODE = "group_by_code";
private final String OBSERVATION_IDENTIFIER_FIELD_NAME = "identifier";
public ElasticsearchSvcImpl(String theHostname, int thePort, String theUsername, String thePassword) {
myRestHighLevelClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(theHostname, thePort, theUsername, thePassword);
try {
createObservationIndexIfMissing();
createObservationCodeIndexIfMissing();
} catch (IOException theE) {
throw new RuntimeException("Failed to create document index", theE);
}
}
private String getIndexSchema(String theSchemaFileName) throws IOException {
InputStreamReader input = new InputStreamReader(ElasticsearchSvcImpl.class.getResourceAsStream(theSchemaFileName));
BufferedReader reader = new BufferedReader(input);
StringBuilder sb = new StringBuilder();
String str;
while((str = reader.readLine())!= null){
sb.append(str);
}
return sb.toString();
}
private void createObservationIndexIfMissing() throws IOException {
if (indexExists(OBSERVATION_INDEX)) {
return;
}
String observationMapping = getIndexSchema(OBSERVATION_INDEX_SCHEMA_FILE);
if (!createIndex(OBSERVATION_INDEX, observationMapping)) {
throw new RuntimeException("Failed to create observation index");
}
}
private void createObservationCodeIndexIfMissing() throws IOException {
if (indexExists(OBSERVATION_CODE_INDEX)) {
return;
}
String observationCodeMapping = getIndexSchema(OBSERVATION_CODE_INDEX_SCHEMA_FILE);
if (!createIndex(OBSERVATION_CODE_INDEX, observationCodeMapping)) {
throw new RuntimeException("Failed to create observation code index");
}
}
private boolean createIndex(String theIndexName, String theMapping) throws IOException {
CreateIndexRequest request = new CreateIndexRequest(theIndexName);
request.source(theMapping, XContentType.JSON);
CreateIndexResponse createIndexResponse = myRestHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
return createIndexResponse.isAcknowledged();
}
private boolean indexExists(String theIndexName) throws IOException {
GetIndexRequest request = new GetIndexRequest();
request.indices(theIndexName);
return myRestHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
}
@Override
public List<String> executeLastN(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, Integer theMaxResultsToFetch) {
String[] topHitsInclude = {OBSERVATION_IDENTIFIER_FIELD_NAME};
return buildAndExecuteSearch(theSearchParameterMap, theFhirContext, topHitsInclude,
ObservationJson::getIdentifier, theMaxResultsToFetch);
}
private <T> List<T> buildAndExecuteSearch(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext,
String[] topHitsInclude, Function<ObservationJson,T> setValue, Integer theMaxResultsToFetch) {
String patientParamName = LastNParameterHelper.getPatientParamName(theFhirContext);
String subjectParamName = LastNParameterHelper.getSubjectParamName(theFhirContext);
List<T> searchResults = new ArrayList<>();
if (theSearchParameterMap.containsKey(patientParamName)
|| theSearchParameterMap.containsKey(subjectParamName)) {
for (String subject : getSubjectReferenceCriteria(patientParamName, subjectParamName, theSearchParameterMap)) {
if (theMaxResultsToFetch != null && searchResults.size() >= theMaxResultsToFetch) {
break;
}
SearchRequest myLastNRequest = buildObservationsSearchRequest(subject, theSearchParameterMap, theFhirContext,
createObservationSubjectAggregationBuilder(theSearchParameterMap.getLastNMax(), topHitsInclude));
try {
SearchResponse lastnResponse = executeSearchRequest(myLastNRequest);
searchResults.addAll(buildObservationList(lastnResponse, setValue, theSearchParameterMap, theFhirContext,
theMaxResultsToFetch));
} catch (IOException theE) {
throw new InvalidRequestException("Unable to execute LastN request", theE);
}
}
} else {
SearchRequest myLastNRequest = buildObservationsSearchRequest(theSearchParameterMap, theFhirContext,
createObservationCodeAggregationBuilder(theSearchParameterMap.getLastNMax(), topHitsInclude));
try {
SearchResponse lastnResponse = executeSearchRequest(myLastNRequest);
searchResults.addAll(buildObservationList(lastnResponse, setValue, theSearchParameterMap, theFhirContext,
theMaxResultsToFetch));
} catch (IOException theE) {
throw new InvalidRequestException("Unable to execute LastN request", theE);
}
}
return searchResults;
}
private List<String> getSubjectReferenceCriteria(String thePatientParamName, String theSubjectParamName, SearchParameterMap theSearchParameterMap) {
List<String> subjectReferenceCriteria = new ArrayList<>();
List<List<IQueryParameterType>> patientParams = new ArrayList<>();
if (theSearchParameterMap.get(thePatientParamName) != null) {
patientParams.addAll(theSearchParameterMap.get(thePatientParamName));
}
if (theSearchParameterMap.get(theSubjectParamName) != null) {
patientParams.addAll(theSearchParameterMap.get(theSubjectParamName));
}
for (List<? extends IQueryParameterType> nextSubjectList : patientParams) {
subjectReferenceCriteria.addAll(getReferenceValues(nextSubjectList));
}
return subjectReferenceCriteria;
}
private List<String> getReferenceValues(List<? extends IQueryParameterType> referenceParams) {
List<String> referenceList = new ArrayList<>();
for (IQueryParameterType nextOr : referenceParams) {
if (nextOr instanceof ReferenceParam) {
ReferenceParam ref = (ReferenceParam) nextOr;
if (isBlank(ref.getChain())) {
referenceList.add(ref.getValue());
}
} else {
throw new IllegalArgumentException("Invalid token type (expecting ReferenceParam): " + nextOr.getClass());
}
}
return referenceList;
}
private CompositeAggregationBuilder createObservationSubjectAggregationBuilder(int theMaxNumberObservationsPerCode, String[] theTopHitsInclude) {
CompositeValuesSourceBuilder<?> subjectValuesBuilder = new TermsValuesSourceBuilder("subject").field("subject");
List<CompositeValuesSourceBuilder<?>> compositeAggSubjectSources = new ArrayList();
compositeAggSubjectSources.add(subjectValuesBuilder);
CompositeAggregationBuilder compositeAggregationSubjectBuilder = new CompositeAggregationBuilder(GROUP_BY_SUBJECT, compositeAggSubjectSources);
compositeAggregationSubjectBuilder.subAggregation(createObservationCodeAggregationBuilder(theMaxNumberObservationsPerCode, theTopHitsInclude));
compositeAggregationSubjectBuilder.size(10000);
return compositeAggregationSubjectBuilder;
}
private TermsAggregationBuilder createObservationCodeAggregationBuilder(int theMaxNumberObservationsPerCode, String[] theTopHitsInclude) {
TermsAggregationBuilder observationCodeCodeAggregationBuilder = new TermsAggregationBuilder(GROUP_BY_CODE, ValueType.STRING).field("codeconceptcodingcode");
observationCodeCodeAggregationBuilder.order(BucketOrder.key(true));
// Top Hits Aggregation
observationCodeCodeAggregationBuilder.subAggregation(AggregationBuilders.topHits("most_recent_effective")
.sort("effectivedtm", SortOrder.DESC)
.fetchSource(theTopHitsInclude, null).size(theMaxNumberObservationsPerCode));
observationCodeCodeAggregationBuilder.size(10000);
TermsAggregationBuilder observationCodeSystemAggregationBuilder = new TermsAggregationBuilder(GROUP_BY_SYSTEM, ValueType.STRING).field("codeconceptcodingsystem");
observationCodeSystemAggregationBuilder.order(BucketOrder.key(true));
observationCodeSystemAggregationBuilder.subAggregation(observationCodeCodeAggregationBuilder);
return observationCodeSystemAggregationBuilder;
}
private SearchResponse executeSearchRequest(SearchRequest searchRequest) throws IOException {
return myRestHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
}
private <T> List<T> buildObservationList(SearchResponse theSearchResponse, Function<ObservationJson,T> setValue,
SearchParameterMap theSearchParameterMap, FhirContext theFhirContext,
Integer theMaxResultsToFetch) throws IOException {
List<T> theObservationList = new ArrayList<>();
if (theSearchParameterMap.containsKey(LastNParameterHelper.getPatientParamName(theFhirContext))
|| theSearchParameterMap.containsKey(LastNParameterHelper.getSubjectParamName(theFhirContext))) {
for (ParsedComposite.ParsedBucket subjectBucket : getSubjectBuckets(theSearchResponse)) {
if (theMaxResultsToFetch != null && theObservationList.size() >= theMaxResultsToFetch) {
break;
}
for (Terms.Bucket observationCodeBucket : getObservationCodeBuckets(subjectBucket)) {
if (theMaxResultsToFetch != null && theObservationList.size() >= theMaxResultsToFetch) {
break;
}
for (SearchHit lastNMatch : getLastNMatches(observationCodeBucket)) {
if (theMaxResultsToFetch != null && theObservationList.size() >= theMaxResultsToFetch) {
break;
}
String indexedObservation = lastNMatch.getSourceAsString();
ObservationJson observationJson = objectMapper.readValue(indexedObservation, ObservationJson.class);
theObservationList.add(setValue.apply(observationJson));
}
}
}
} else {
for (Terms.Bucket observationCodeBucket : getObservationCodeBuckets(theSearchResponse)) {
if (theMaxResultsToFetch != null && theObservationList.size() >= theMaxResultsToFetch) {
break;
}
for (SearchHit lastNMatch : getLastNMatches(observationCodeBucket)) {
if (theMaxResultsToFetch != null && theObservationList.size() >= theMaxResultsToFetch) {
break;
}
String indexedObservation = lastNMatch.getSourceAsString();
ObservationJson observationJson = objectMapper.readValue(indexedObservation, ObservationJson.class);
theObservationList.add(setValue.apply(observationJson));
}
}
}
return theObservationList;
}
private List<ParsedComposite.ParsedBucket> getSubjectBuckets(SearchResponse theSearchResponse) {
Aggregations responseAggregations = theSearchResponse.getAggregations();
ParsedComposite aggregatedSubjects = responseAggregations.get(GROUP_BY_SUBJECT);
return aggregatedSubjects.getBuckets();
}
private List<? extends Terms.Bucket> getObservationCodeBuckets(SearchResponse theSearchResponse) {
Aggregations responseAggregations = theSearchResponse.getAggregations();
return getObservationCodeBuckets(responseAggregations);
}
private List<? extends Terms.Bucket> getObservationCodeBuckets(ParsedComposite.ParsedBucket theSubjectBucket) {
Aggregations observationCodeSystemAggregations = theSubjectBucket.getAggregations();
return getObservationCodeBuckets(observationCodeSystemAggregations);
}
private List<? extends Terms.Bucket> getObservationCodeBuckets(Aggregations theObservationCodeSystemAggregations) {
List<Terms.Bucket> retVal = new ArrayList<>();
ParsedTerms aggregatedObservationCodeSystems = theObservationCodeSystemAggregations.get(GROUP_BY_SYSTEM);
for(Terms.Bucket observationCodeSystem : aggregatedObservationCodeSystems.getBuckets()) {
Aggregations observationCodeCodeAggregations = observationCodeSystem.getAggregations();
ParsedTerms aggregatedObservationCodeCodes = observationCodeCodeAggregations.get(GROUP_BY_CODE);
retVal.addAll(aggregatedObservationCodeCodes.getBuckets());
}
return retVal;
}
private SearchHit[] getLastNMatches(Terms.Bucket theObservationCodeBucket) {
Aggregations topHitObservationCodes = theObservationCodeBucket.getAggregations();
ParsedTopHits parsedTopHits = topHitObservationCodes.get("most_recent_effective");
return parsedTopHits.getHits().getHits();
}
private SearchRequest buildObservationsSearchRequest(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, AggregationBuilder theAggregationBuilder) {
SearchRequest searchRequest = new SearchRequest(OBSERVATION_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// Query
if (!searchParamsHaveLastNCriteria(theSearchParameterMap, theFhirContext)) {
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
} else {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
addCategoriesCriteria(boolQueryBuilder, theSearchParameterMap, theFhirContext);
addObservationCodeCriteria(boolQueryBuilder, theSearchParameterMap, theFhirContext);
searchSourceBuilder.query(boolQueryBuilder);
}
searchSourceBuilder.size(0);
// Aggregation by order codes
searchSourceBuilder.aggregation(theAggregationBuilder);
searchRequest.source(searchSourceBuilder);
return searchRequest;
}
private SearchRequest buildObservationsSearchRequest(String theSubjectParam, SearchParameterMap theSearchParameterMap, FhirContext theFhirContext,
AggregationBuilder theAggregationBuilder) {
SearchRequest searchRequest = new SearchRequest(OBSERVATION_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// Query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.termQuery("subject", theSubjectParam));
addCategoriesCriteria(boolQueryBuilder, theSearchParameterMap, theFhirContext);
addObservationCodeCriteria(boolQueryBuilder, theSearchParameterMap, theFhirContext);
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(0);
// Aggregation by order codes
searchSourceBuilder.aggregation(theAggregationBuilder);
searchRequest.source(searchSourceBuilder);
return searchRequest;
}
private Boolean searchParamsHaveLastNCriteria(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext) {
return theSearchParameterMap != null &&
(theSearchParameterMap.containsKey(LastNParameterHelper.getPatientParamName(theFhirContext))
|| theSearchParameterMap.containsKey(LastNParameterHelper.getSubjectParamName(theFhirContext))
|| theSearchParameterMap.containsKey(LastNParameterHelper.getCategoryParamName(theFhirContext))
|| theSearchParameterMap.containsKey(LastNParameterHelper.getCodeParamName(theFhirContext)));
}
private void addCategoriesCriteria(BoolQueryBuilder theBoolQueryBuilder, SearchParameterMap theSearchParameterMap, FhirContext theFhirContext) {
String categoryParamName = LastNParameterHelper.getCategoryParamName(theFhirContext);
if (theSearchParameterMap.containsKey(categoryParamName)) {
ArrayList<String> codeSystemHashList = new ArrayList<>();
ArrayList<String> codeOnlyList = new ArrayList<>();
ArrayList<String> systemOnlyList = new ArrayList<>();
ArrayList<String> textOnlyList = new ArrayList<>();
List<List<IQueryParameterType>> andOrParams = theSearchParameterMap.get(categoryParamName);
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
codeSystemHashList.addAll(getCodingCodeSystemValues(nextAnd));
codeOnlyList.addAll(getCodingCodeOnlyValues(nextAnd));
systemOnlyList.addAll(getCodingSystemOnlyValues(nextAnd));
textOnlyList.addAll(getCodingTextOnlyValues(nextAnd));
}
if (codeSystemHashList.size() > 0) {
theBoolQueryBuilder.must(QueryBuilders.termsQuery("categoryconceptcodingcode_system_hash", codeSystemHashList));
}
if (codeOnlyList.size() > 0) {
theBoolQueryBuilder.must(QueryBuilders.termsQuery("categoryconceptcodingcode", codeOnlyList));
}
if (systemOnlyList.size() > 0) {
theBoolQueryBuilder.must(QueryBuilders.termsQuery("categoryconceptcodingsystem", systemOnlyList));
}
if (textOnlyList.size() > 0) {
BoolQueryBuilder myTextBoolQueryBuilder = QueryBuilders.boolQuery();
for (String textOnlyParam : textOnlyList) {
myTextBoolQueryBuilder.should(QueryBuilders.matchPhraseQuery("categoryconceptcodingdisplay", textOnlyParam));
myTextBoolQueryBuilder.should(QueryBuilders.matchPhraseQuery("categoryconcepttext", textOnlyParam));
}
theBoolQueryBuilder.must(myTextBoolQueryBuilder);
}
}
}
private List<String> getCodingCodeSystemValues(List<? extends IQueryParameterType> codeParams) {
ArrayList<String> codeSystemHashList = new ArrayList<>();
for (IQueryParameterType nextOr : codeParams) {
if (nextOr instanceof TokenParam) {
TokenParam ref = (TokenParam) nextOr;
if (ref.getSystem() != null && ref.getValue() != null) {
codeSystemHashList.add(String.valueOf(CodeSystemHash.hashCodeSystem(ref.getSystem(), ref.getValue())));
}
} else {
throw new IllegalArgumentException("Invalid token type (expecting TokenParam): " + nextOr.getClass());
}
}
return codeSystemHashList;
}
private List<String> getCodingCodeOnlyValues(List<? extends IQueryParameterType> codeParams) {
ArrayList<String> codeOnlyList = new ArrayList<>();
for (IQueryParameterType nextOr : codeParams) {
if (nextOr instanceof TokenParam) {
TokenParam ref = (TokenParam) nextOr;
if (ref.getValue() != null && ref.getSystem() == null && !ref.isText()) {
codeOnlyList.add(ref.getValue());
}
} else {
throw new IllegalArgumentException("Invalid token type (expecting TokenParam): " + nextOr.getClass());
}
}
return codeOnlyList;
}
private List<String> getCodingSystemOnlyValues(List<? extends IQueryParameterType> codeParams) {
ArrayList<String> systemOnlyList = new ArrayList<>();
for (IQueryParameterType nextOr : codeParams) {
if (nextOr instanceof TokenParam) {
TokenParam ref = (TokenParam) nextOr;
if (ref.getValue() == null && ref.getSystem() != null) {
systemOnlyList.add(ref.getSystem());
}
} else {
throw new IllegalArgumentException("Invalid token type (expecting TokenParam): " + nextOr.getClass());
}
}
return systemOnlyList;
}
private List<String> getCodingTextOnlyValues(List<? extends IQueryParameterType> codeParams) {
ArrayList<String> textOnlyList = new ArrayList<>();
for (IQueryParameterType nextOr : codeParams) {
if (nextOr instanceof TokenParam) {
TokenParam ref = (TokenParam) nextOr;
if (ref.isText() && ref.getValue() != null) {
textOnlyList.add(ref.getValue());
}
} else {
throw new IllegalArgumentException("Invalid token type (expecting TokenParam): " + nextOr.getClass());
}
}
return textOnlyList;
}
private void addObservationCodeCriteria(BoolQueryBuilder theBoolQueryBuilder, SearchParameterMap theSearchParameterMap, FhirContext theFhirContext) {
String codeParamName = LastNParameterHelper.getCodeParamName(theFhirContext);
if (theSearchParameterMap.containsKey(codeParamName)) {
ArrayList<String> codeSystemHashList = new ArrayList<>();
ArrayList<String> codeOnlyList = new ArrayList<>();
ArrayList<String> systemOnlyList = new ArrayList<>();
ArrayList<String> textOnlyList = new ArrayList<>();
List<List<IQueryParameterType>> andOrParams = theSearchParameterMap.get(codeParamName);
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
codeSystemHashList.addAll(getCodingCodeSystemValues(nextAnd));
codeOnlyList.addAll(getCodingCodeOnlyValues(nextAnd));
systemOnlyList.addAll(getCodingSystemOnlyValues(nextAnd));
textOnlyList.addAll(getCodingTextOnlyValues(nextAnd));
}
if (codeSystemHashList.size() > 0) {
theBoolQueryBuilder.must(QueryBuilders.termsQuery("codeconceptcodingcode_system_hash", codeSystemHashList));
}
if (codeOnlyList.size() > 0) {
theBoolQueryBuilder.must(QueryBuilders.termsQuery("codeconceptcodingcode", codeOnlyList));
}
if (systemOnlyList.size() > 0) {
theBoolQueryBuilder.must(QueryBuilders.termsQuery("codeconceptcodingsystem", systemOnlyList));
}
if (textOnlyList.size() > 0) {
BoolQueryBuilder myTextBoolQueryBuilder = QueryBuilders.boolQuery();
for (String textOnlyParam : textOnlyList) {
myTextBoolQueryBuilder.should(QueryBuilders.matchPhraseQuery("codeconceptcodingdisplay", textOnlyParam));
myTextBoolQueryBuilder.should(QueryBuilders.matchPhraseQuery("codeconcepttext", textOnlyParam));
}
theBoolQueryBuilder.must(myTextBoolQueryBuilder);
}
}
}
@VisibleForTesting
List<ObservationJson> executeLastNWithAllFields(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext) {
return buildAndExecuteSearch(theSearchParameterMap, theFhirContext, null, t -> t, 100);
}
@VisibleForTesting
List<CodeJson> queryAllIndexedObservationCodes() throws IOException {
SearchRequest codeSearchRequest = new SearchRequest(OBSERVATION_CODE_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// Query
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchSourceBuilder.size(1000);
codeSearchRequest.source(searchSourceBuilder);
SearchResponse codeSearchResponse = executeSearchRequest(codeSearchRequest);
return buildCodeResult(codeSearchResponse);
}
private List<CodeJson> buildCodeResult(SearchResponse theSearchResponse) throws JsonProcessingException {
SearchHits codeHits = theSearchResponse.getHits();
List<CodeJson> codes = new ArrayList<>();
for (SearchHit codeHit : codeHits) {
CodeJson code = objectMapper.readValue(codeHit.getSourceAsString(), CodeJson.class);
codes.add(code);
}
return codes;
}
@VisibleForTesting
boolean performIndex(String theIndexName, String theDocumentId, String theIndexDocument, String theDocumentType) throws IOException {
IndexResponse indexResponse = myRestHighLevelClient.index(createIndexRequest(theIndexName, theDocumentId, theIndexDocument, theDocumentType),
RequestOptions.DEFAULT);
return (indexResponse.getResult() == DocWriteResponse.Result.CREATED) || (indexResponse.getResult() == DocWriteResponse.Result.UPDATED);
}
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;
}
@VisibleForTesting
void deleteAllDocuments(String theIndexName) throws IOException {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(theIndexName);
deleteByQueryRequest.setQuery(QueryBuilders.matchAllQuery());
myRestHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
}
}

View File

@ -0,0 +1,18 @@
package ca.uhn.fhir.jpa.search.lastn;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import java.util.List;
public interface IElasticsearchSvc {
/**
* Returns identifiers for the last most recent N observations that meet the specified criteria.
* @param theSearchParameterMap SearchParameterMap containing search parameters used for filtering the last N observations. Supported parameters include Subject, Patient, Code, Category and Max (the parameter used to determine N).
* @param theFhirContext Current FhirContext.
* @param theMaxResultsToFetch The maximum number of results to return for the purpose of paging.
* @return
*/
List<String> executeLastN(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, Integer theMaxResultsToFetch);
}

View File

@ -0,0 +1,82 @@
package ca.uhn.fhir.jpa.search.lastn.json;
/*
* #%L
* Smile CDR - CDR
* %%
* Copyright (C) 2016 - 2019 Simpatico Intelligent Systems Inc
* %%
* All rights reserved.
* #L%
*/
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import java.util.ArrayList;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class CodeJson {
@JsonProperty(value = "codeable_concept_id", required = false)
private String myCodeableConceptId;
@JsonProperty(value = "text", required = false)
private String myCodeableConceptText;
@JsonProperty(value = "codingcode", required = false)
private List<String> myCoding_code = new ArrayList<>();
@JsonProperty(value = "codingcode_system_hash", required = true)
private List<String> myCoding_code_system_hash = new ArrayList<>();
@JsonProperty(value = "codingdisplay", required = false)
private List<String> myCoding_display = new ArrayList<>();
@JsonProperty(value = "codingsystem", required = false)
private List<String> myCoding_system = new ArrayList<>();
public CodeJson(){
}
public CodeJson(CodeableConcept theCodeableConcept, String theCodeableConceptId) {
myCodeableConceptText = theCodeableConcept.getText();
myCodeableConceptId = theCodeableConceptId;
for (Coding theCoding : theCodeableConcept.getCoding()) {
myCoding_code.add(theCoding.getCode());
myCoding_system.add(theCoding.getSystem());
myCoding_display.add(theCoding.getDisplay());
myCoding_code_system_hash.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCoding.getSystem(), theCoding.getCode())));
}
}
public String getCodeableConceptId() {
return myCodeableConceptId;
}
public String getCodeableConceptText() {
return myCodeableConceptText;
}
public List<String> getCoding_code() {
return myCoding_code;
}
public List<String> getCoding_code_system_hash() {
return myCoding_code_system_hash;
}
public List<String> getCoding_display() {
return myCoding_display;
}
public List<String> getCoding_system() {
return myCoding_system;
}
}

View File

@ -0,0 +1,176 @@
package ca.uhn.fhir.jpa.search.lastn.json;
/*
* #%L
* Smile CDR - CDR
* %%
* Copyright (C) 2016 - 2019 Simpatico Intelligent Systems Inc
* %%
* All rights reserved.
* #L%
*/
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ObservationJson {
@JsonProperty(value = "identifier", required = true)
private String myIdentifier;
@JsonProperty(value = "subject", required = true)
private String mySubject;
@JsonProperty(value = "categoryconcepttext", required = false)
private List<String> myCategory_concept_text = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingcode", required = false)
private List<List<String>> myCategory_coding_code = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingcode_system_hash", required = false)
private List<List<String>> myCategory_coding_code_system_hash = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingdisplay", required = false)
private List<List<String>> myCategory_coding_display = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingsystem", required = false)
private List<List<String>> myCategory_coding_system = new ArrayList<>();
@JsonProperty(value = "codeconceptid", required = false)
private String myCode_concept_id;
@JsonProperty(value = "codeconcepttext", required = false)
private String myCode_concept_text;
@JsonProperty(value = "codeconceptcodingcode", required = false)
private String myCode_coding_code;
@JsonProperty(value = "codeconceptcodingcode_system_hash", required = false)
private String myCode_coding_code_system_hash;
@JsonProperty(value = "codeconceptcodingdisplay", required = false)
private String myCode_coding_display;
@JsonProperty(value = "codeconceptcodingsystem", required = false)
private String myCode_coding_system;
@JsonProperty(value = "effectivedtm", required = true)
private Date myEffectiveDtm;
public ObservationJson() {
}
public void setIdentifier(String theIdentifier) {
myIdentifier = theIdentifier;
}
public void setSubject(String theSubject) {
mySubject = theSubject;
}
public void setCategories(List<CodeableConcept> theCategories) {
for (CodeableConcept theConcept : theCategories) {
myCategory_concept_text.add(theConcept.getText());
List<String> coding_code_system_hashes = new ArrayList<>();
List<String> coding_codes = new ArrayList<>();
List<String> coding_displays = new ArrayList<>();
List<String> coding_systems = new ArrayList<>();
for (Coding theCategoryCoding : theConcept.getCoding()) {
coding_code_system_hashes.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCategoryCoding.getSystem(), theCategoryCoding.getCode())));
coding_codes.add(theCategoryCoding.getCode());
coding_displays.add(theCategoryCoding.getDisplay());
coding_systems.add(theCategoryCoding.getSystem());
}
myCategory_coding_code_system_hash.add(coding_code_system_hashes);
myCategory_coding_code.add(coding_codes);
myCategory_coding_display.add(coding_displays);
myCategory_coding_system.add(coding_systems);
}
}
public List<String> getCategory_concept_text() {
return myCategory_concept_text;
}
public List<List<String>> getCategory_coding_code_system_hash() {
return myCategory_coding_code_system_hash;
}
public List<List<String>> getCategory_coding_code() {
return myCategory_coding_code;
}
public List<List<String>> getCategory_coding_display() {
return myCategory_coding_display;
}
public List<List<String>> getCategory_coding_system() {
return myCategory_coding_system;
}
public void setCode(CodeableConcept theCode) {
myCode_concept_text = theCode.getText();
for (Coding theCodeCoding : theCode.getCoding()) {
myCode_coding_code_system_hash = String.valueOf(CodeSystemHash.hashCodeSystem(theCodeCoding.getSystem(), theCodeCoding.getCode()));
myCode_coding_code = theCodeCoding.getCode();
myCode_coding_display = theCodeCoding.getDisplay();
myCode_coding_system = theCodeCoding.getSystem();
}
}
public String getCode_concept_text() {
return myCode_concept_text;
}
public String getCode_coding_code_system_hash() {
return myCode_coding_code_system_hash;
}
public String getCode_coding_code() {
return myCode_coding_code;
}
public String getCode_coding_display() {
return myCode_coding_display;
}
public String getCode_coding_system() {
return myCode_coding_system;
}
public void setCode_concept_id(String theCodeId) {
myCode_concept_id = theCodeId;
}
public String getCode_concept_id() {
return myCode_concept_id;
}
public void setEffectiveDtm(Date theEffectiveDtm) {
myEffectiveDtm = theEffectiveDtm;
}
public Date getEffectiveDtm() {
return myEffectiveDtm;
}
public String getSubject() {
return mySubject;
}
public String getIdentifier() {
return myIdentifier;
}
}

View File

@ -34,8 +34,8 @@ import java.util.function.Consumer;
public class QueryChunker<T> {
public void chunk(List<T> theInput, Consumer<List<T>> theBatchConsumer) {
for (int i = 0; i < theInput.size(); i += SearchBuilder.MAXIMUM_PAGE_SIZE) {
int to = i + SearchBuilder.MAXIMUM_PAGE_SIZE;
for (int i = 0; i < theInput.size(); i += SearchBuilder.getMaximumPageSize()) {
int to = i + SearchBuilder.getMaximumPageSize();
to = Math.min(to, theInput.size());
List<T> batch = theInput.subList(i, to);
theBatchConsumer.accept(batch);

View File

@ -131,13 +131,15 @@ 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;
Validate.isTrue(
hasEmbedded ||
hasColumn ||
hasJoinColumn ||
isOtherSideOfOneToManyMapping ||
isOtherSideOfOneToOneMapping ||
hasEmbeddedId, "Non-transient has no @Column or @JoinColumn or @EmbeddedId: " + nextField);
hasEmbeddedId ||
isField, "Non-transient has no @Column or @JoinColumn or @EmbeddedId: " + nextField);
}

View File

@ -0,0 +1,26 @@
{
"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"
}
}
}
}
}

View File

@ -0,0 +1,50 @@
{
"mappings" : {
"ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity" : {
"properties" : {
"codeconceptid" : {
"type" : "keyword"
},
"codeconcepttext" : {
"type" : "text"
},
"codeconceptcodingcode" : {
"type" : "keyword"
},
"codeconceptcodingsystem" : {
"type" : "keyword"
},
"codeconceptcodingcode_system_hash" : {
"type" : "keyword"
},
"codeconceptcodingdisplay" : {
"type" : "keyword"
},
"categoryconcepttext" : {
"type" : "keyword"
},
"categoryconceptcodingcode" : {
"type" : "keyword"
},
"categoryconceptcodingsystem" : {
"type" : "keyword"
},
"categoryconceptcodingcode_system_hash" : {
"type" : "keyword"
},
"categoryconceptcodingdisplay" : {
"type" : "keyword"
},
"effectivedtm" : {
"type" : "date"
},
"identifier" : {
"type" : "keyword"
},
"subject" : {
"type" : "keyword"
}
}
}
}
}

View File

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

View File

@ -22,6 +22,10 @@ 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
@ -38,9 +42,9 @@ public class TestR4ConfigWithElasticSearch extends TestR4Config {
.setIndexSchemaManagementStrategy(IndexSchemaManagementStrategy.CREATE)
.setIndexManagementWaitTimeoutMillis(10000)
.setRequiredIndexStatus(ElasticsearchIndexStatus.YELLOW)
.setRestUrl("http://localhost:" + httpPort)
.setUsername("")
.setPassword("")
.setRestUrl("http://"+ elasticsearchHost + ":" + httpPort)
.setUsername(elasticsearchUserId)
.setPassword(elasticsearchPassword)
.apply(retVal);
return retVal;
@ -65,7 +69,6 @@ public class TestR4ConfigWithElasticSearch extends TestR4Config {
return embeddedElastic;
}
@PreDestroy
public void stop() {
embeddedElasticSearch().stop();

View File

@ -0,0 +1,16 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestR4ConfigWithElasticsearchClient extends TestR4ConfigWithElasticSearch {
@Bean()
public ElasticsearchSvcImpl myElasticsearchSvc() {
int elasticsearchPort = embeddedElasticSearch().getHttpPort();
return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUserId, elasticsearchPassword);
}
}

View File

@ -432,6 +432,145 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
}
@Test
public void testDatePeriodParamEndOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params;
List<Encounter> encs;
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
// encs = toList(ourEncounterDao.search(params));
// assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartAndEnd() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("03");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-03");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("01");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
}
@Test
public void testSearchCompositeParam() {
Observation o1 = new Observation();

View File

@ -584,143 +584,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
}
}
@Test
public void testDatePeriodParamEndOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params;
List<Encounter> encs;
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
// encs = toList(ourEncounterDao.search(params));
// assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartAndEnd() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("03");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-03");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("01");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDeleteFailsIfIncomingLinks() {

View File

@ -959,6 +959,145 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
}
@Test
public void testDatePeriodParamEndOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params;
List<Encounter> encs;
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
// encs = toList(ourEncounterDao.search(params));
// assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartAndEnd() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("03");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-03");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("01");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
}
/**
* #222
*/

View File

@ -870,143 +870,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
}
@Test
public void testDatePeriodParamEndOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params;
List<Encounter> encs;
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
// encs = toList(ourEncounterDao.search(params));
// assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartAndEnd() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("03");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-03");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("01");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDeleteFailsIfIncomingLinks() {

View File

@ -0,0 +1,543 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.config.TestR4ConfigWithElasticsearchClient;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.RestfulServer;
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.DateTimeType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestR4ConfigWithElasticsearchClient.class })
public class BaseR4SearchLastN extends BaseJpaTest {
@Autowired
@Qualifier("myPatientDaoR4")
protected IFhirResourceDaoPatient<Patient> myPatientDao;
@Autowired
@Qualifier("myObservationDaoR4")
protected IFhirResourceDaoObservation<Observation> myObservationDao;
@Autowired
protected DaoConfig myDaoConfig;
@Autowired
protected FhirContext myFhirCtx;
@Autowired
protected PlatformTransactionManager myPlatformTransactionManager;
@Override
protected FhirContext getContext() {
return myFhirCtx;
}
@Override
protected PlatformTransactionManager getTxManager() {
return myPlatformTransactionManager;
}
protected final String observationCd0 = "code0";
protected final String observationCd1 = "code1";
protected final String observationCd2 = "code2";
private final String observationCd3 = "code3";
protected final String categoryCd0 = "category0";
private final String categoryCd1 = "category1";
private final String categoryCd2 = "category2";
private final String categoryCd3 = "category3";
protected final String codeSystem = "http://mycode.com";
private final String categorySystem = "http://mycategory.com";
// Using static variables including the flag below so that we can initalize the database and indexes once
// (all of the tests only read from the DB and indexes and so no need to re-initialze them for each test).
private static boolean dataLoaded = false;
protected static IIdType patient0Id = null;
protected static IIdType patient1Id = null;
protected static IIdType patient2Id = null;
private static final Map<String, String> observationPatientMap = new HashMap<>();
private static final Map<String, String> observationCategoryMap = new HashMap<>();
private static final Map<String, String> observationCodeMap = new HashMap<>();
private static final Map<String, Date> observationEffectiveMap = new HashMap<>();
@Before
public void beforeCreateTestPatientsAndObservations() {
// Using a static flag to ensure that test data and elasticsearch index is only created once.
// Creating this data and the index is time consuming and as such want to avoid having to repeat for each test.
// Normally would use a static @BeforeClass method for this purpose, but Autowired objects cannot be accessed in static methods.
if(!dataLoaded) {
Patient pt = new Patient();
pt.addName().setFamily("Lastn").addGiven("Arthur");
patient0Id = myPatientDao.create(pt, mockSrd()).getId().toUnqualifiedVersionless();
createObservationsForPatient(patient0Id);
pt = new Patient();
pt.addName().setFamily("Lastn").addGiven("Johnathan");
patient1Id = myPatientDao.create(pt, mockSrd()).getId().toUnqualifiedVersionless();
createObservationsForPatient(patient1Id);
pt = new Patient();
pt.addName().setFamily("Lastn").addGiven("Michael");
patient2Id = myPatientDao.create(pt, mockSrd()).getId().toUnqualifiedVersionless();
createObservationsForPatient(patient2Id);
dataLoaded = true;
}
}
private void createObservationsForPatient(IIdType thePatientId) {
createFiveObservationsForPatientCodeCategory(thePatientId,observationCd0, categoryCd0, 15);
createFiveObservationsForPatientCodeCategory(thePatientId,observationCd0, categoryCd1, 10);
createFiveObservationsForPatientCodeCategory(thePatientId,observationCd0, categoryCd2, 5);
createFiveObservationsForPatientCodeCategory(thePatientId,observationCd1, categoryCd0, 10);
createFiveObservationsForPatientCodeCategory(thePatientId,observationCd1, categoryCd1, 5);
createFiveObservationsForPatientCodeCategory(thePatientId,observationCd2, categoryCd2, 5);
createFiveObservationsForPatientCodeCategory(thePatientId,observationCd3, categoryCd3, 5);
}
private void createFiveObservationsForPatientCodeCategory(IIdType thePatientId, String theObservationCode, String theCategoryCode,
Integer theTimeOffset) {
Calendar observationDate = new GregorianCalendar();
for (int idx=0; idx<5; idx++ ) {
Observation obs = new Observation();
obs.getSubject().setReferenceElement(thePatientId);
obs.getCode().addCoding().setCode(theObservationCode).setSystem(codeSystem);
obs.setValue(new StringType(theObservationCode + "_0"));
observationDate.add(Calendar.HOUR, -theTimeOffset+idx);
Date effectiveDtm = observationDate.getTime();
obs.setEffective(new DateTimeType(effectiveDtm));
obs.getCategoryFirstRep().addCoding().setCode(theCategoryCode).setSystem(categorySystem);
String observationId = myObservationDao.create(obs, mockSrd()).getId().toUnqualifiedVersionless().getValue();
observationPatientMap.put(observationId, thePatientId.getValue());
observationCategoryMap.put(observationId, theCategoryCode);
observationCodeMap.put(observationId, theObservationCode);
observationEffectiveMap.put(observationId, effectiveDtm);
}
}
protected ServletRequestDetails mockSrd() {
return mySrd;
}
@Test
public void testLastNAllPatients() {
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedPatients.add(patient1Id.getValue());
sortedPatients.add(patient2Id.getValue());
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd1);
sortedObservationCodes.add(observationCd2);
sortedObservationCodes.add(observationCd3);
executeTestCase(params, sortedPatients, sortedObservationCodes, null,105);
}
@Test
public void testLastNNoPatients() {
SearchParameterMap params = new SearchParameterMap();
params.setLastNMax(1);
params.setLastN(true);
Map<String, String[]> requestParameters = new HashMap<>();
when(mySrd.getParameters()).thenReturn(requestParameters);
List<String> actual = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null));
assertEquals(4, actual.size());
}
private void executeTestCase(SearchParameterMap params, List<String> sortedPatients, List<String> sortedObservationCodes, List<String> theCategories, int expectedObservationCount) {
List<String> actual;
params.setLastN(true);
Map<String, String[]> requestParameters = new HashMap<>();
params.setLastNMax(100);
when(mySrd.getParameters()).thenReturn(requestParameters);
actual = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null));
assertEquals(expectedObservationCount, actual.size());
validateSorting(actual, sortedPatients, sortedObservationCodes, theCategories);
}
private void validateSorting(List<String> theObservationIds, List<String> thePatientIds, List<String> theCodes, List<String> theCategores) {
int theNextObservationIdx = 0;
// Validate patient grouping
for (String patientId : thePatientIds) {
assertEquals(patientId, observationPatientMap.get(theObservationIds.get(theNextObservationIdx)));
theNextObservationIdx = validateSortingWithinPatient(theObservationIds,theNextObservationIdx,theCodes, theCategores, patientId);
}
assertEquals(theObservationIds.size(), theNextObservationIdx);
}
private int validateSortingWithinPatient(List<String> theObservationIds, int theFirstObservationIdxForPatient, List<String> theCodes,
List<String> theCategories, String thePatientId) {
int theNextObservationIdx = theFirstObservationIdxForPatient;
for (String codeValue : theCodes) {
assertEquals(codeValue, observationCodeMap.get(theObservationIds.get(theNextObservationIdx)));
// Validate sorting within code group
theNextObservationIdx = validateSortingWithinCode(theObservationIds,theNextObservationIdx,
observationCodeMap.get(theObservationIds.get(theNextObservationIdx)), theCategories, thePatientId);
}
return theNextObservationIdx;
}
private int validateSortingWithinCode(List<String> theObservationIds, int theFirstObservationIdxForPatientAndCode, String theObservationCode,
List<String> theCategories, String thePatientId) {
int theNextObservationIdx = theFirstObservationIdxForPatientAndCode;
Date lastEffectiveDt = observationEffectiveMap.get(theObservationIds.get(theNextObservationIdx));
theNextObservationIdx++;
while(theObservationCode.equals(observationCodeMap.get(theObservationIds.get(theNextObservationIdx)))
&& thePatientId.equals(observationPatientMap.get(theObservationIds.get(theNextObservationIdx)))) {
// Check that effective date is before that of the previous observation.
assertTrue(lastEffectiveDt.compareTo(observationEffectiveMap.get(theObservationIds.get(theNextObservationIdx))) > 0);
lastEffectiveDt = observationEffectiveMap.get(theObservationIds.get(theNextObservationIdx));
// Check that observation is in one of the specified categories (if applicable)
if (theCategories != null && !theCategories.isEmpty()) {
assertTrue(theCategories.contains(observationCategoryMap.get(theObservationIds.get(theNextObservationIdx))));
}
theNextObservationIdx++;
if (theNextObservationIdx >= theObservationIds.size()) {
// Have reached the end of the Observation list.
break;
}
}
return theNextObservationIdx;
}
@Test
public void testLastNSinglePatient() {
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", patient0Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd1);
sortedObservationCodes.add(observationCd2);
sortedObservationCodes.add(observationCd3);
executeTestCase(params, sortedPatients,sortedObservationCodes, null,35);
params = new SearchParameterMap();
ReferenceParam patientParam = new ReferenceParam("Patient", "", patient0Id.getValue());
params.add(Observation.SP_PATIENT, buildReferenceAndListParam(patientParam));
sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd1);
sortedObservationCodes.add(observationCd2);
sortedObservationCodes.add(observationCd3);
executeTestCase(params, sortedPatients,sortedObservationCodes, null,35);
}
protected ReferenceAndListParam buildReferenceAndListParam(ReferenceParam... theReference) {
ReferenceOrListParam myReferenceOrListParam = new ReferenceOrListParam();
for (ReferenceParam referenceParam : theReference) {
myReferenceOrListParam.addOr(referenceParam);
}
return new ReferenceAndListParam().addAnd(myReferenceOrListParam);
}
@Test
public void testLastNMultiplePatients() {
// Two Subject parameters.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2));
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedPatients.add(patient1Id.getValue());
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd1);
sortedObservationCodes.add(observationCd2);
sortedObservationCodes.add(observationCd3);
executeTestCase(params, sortedPatients, sortedObservationCodes, null,70);
// Two Patient parameters
params = new SearchParameterMap();
ReferenceParam patientParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam patientParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(patientParam1, patientParam3));
sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedPatients.add(patient2Id.getValue());
executeTestCase(params,sortedPatients, sortedObservationCodes, null,70);
}
@Test
public void testLastNSingleCategory() {
// One category parameter.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam categoryParam = new TokenParam(categorySystem, categoryCd0);
params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
List<String> myCategories = new ArrayList<>();
myCategories.add(categoryCd0);
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedPatients.add(patient1Id.getValue());
sortedPatients.add(patient2Id.getValue());
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd1);
executeTestCase(params, sortedPatients, sortedObservationCodes, myCategories, 30);
// Another category parameter.
params = new SearchParameterMap();
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
categoryParam = new TokenParam(categorySystem, categoryCd2);
params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
myCategories = new ArrayList<>();
myCategories.add(categoryCd2);
sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd2);
executeTestCase(params, sortedPatients, sortedObservationCodes, myCategories, 30);
}
@Test
public void testLastNMultipleCategories() {
// Two category parameters.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam categoryParam1 = new TokenParam(categorySystem, categoryCd0);
TokenParam categoryParam2 = new TokenParam(categorySystem, categoryCd1);
params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam1, categoryParam2));
List<String> myCategories = new ArrayList<>();
myCategories.add(categoryCd0);
myCategories.add(categoryCd1);
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedPatients.add(patient1Id.getValue());
sortedPatients.add(patient2Id.getValue());
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd1);
executeTestCase(params, sortedPatients, sortedObservationCodes, myCategories, 60);
}
@Test
public void testLastNSingleCode() {
// One code parameter.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam code = new TokenParam(codeSystem, observationCd0);
params.add(Observation.SP_CODE, buildTokenAndListParam(code));
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedPatients.add(patient1Id.getValue());
sortedPatients.add(patient2Id.getValue());
executeTestCase(params, sortedPatients, sortedObservationCodes, null, 45);
// Another code parameter.
params = new SearchParameterMap();
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
code = new TokenParam(codeSystem, observationCd2);
params.add(Observation.SP_CODE, buildTokenAndListParam(code));
sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd2);
executeTestCase(params, sortedPatients, sortedObservationCodes, null, 15);
}
@Test
public void testLastNMultipleCodes() {
// Two code parameters.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam codeParam1 = new TokenParam(codeSystem, observationCd0);
TokenParam codeParam2 = new TokenParam(codeSystem, observationCd1);
params.add(Observation.SP_CODE, buildTokenAndListParam(codeParam1, codeParam2));
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd1);
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedPatients.add(patient1Id.getValue());
sortedPatients.add(patient2Id.getValue());
executeTestCase(params, sortedPatients, sortedObservationCodes, null, 75);
}
@Test
public void testLastNSinglePatientCategoryCode() {
// One patient, category and code.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", patient0Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
TokenParam code = new TokenParam(codeSystem, observationCd0);
params.add(Observation.SP_CODE, buildTokenAndListParam(code));
TokenParam category = new TokenParam(categorySystem, categoryCd2);
params.add(Observation.SP_CATEGORY, buildTokenAndListParam(category));
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
List<String> myCategories = new ArrayList<>();
myCategories.add(categoryCd2);
executeTestCase(params, sortedPatients, sortedObservationCodes, myCategories, 5);
}
@Test
public void testLastNMultiplePatientsCategoriesCodes() {
// Two patients, categories and codes.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2));
List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue());
sortedPatients.add(patient1Id.getValue());
TokenParam codeParam1 = new TokenParam(codeSystem, observationCd0);
TokenParam codeParam2 = new TokenParam(codeSystem, observationCd2);
params.add(Observation.SP_CODE, buildTokenAndListParam(codeParam1, codeParam2));
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd2);
TokenParam categoryParam1 = new TokenParam(categorySystem, categoryCd1);
TokenParam categoryParam2 = new TokenParam(categorySystem, categoryCd2);
params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam1, categoryParam2));
List<String> myCategories = new ArrayList<>();
myCategories.add(categoryCd1);
myCategories.add(categoryCd2);
executeTestCase(params, sortedPatients, sortedObservationCodes, myCategories, 30);
}
protected TokenAndListParam buildTokenAndListParam(TokenParam... theToken) {
TokenOrListParam myTokenOrListParam = new TokenOrListParam();
for (TokenParam tokenParam : theToken) {
myTokenOrListParam.addOr(tokenParam);
}
return new TokenAndListParam().addAnd(myTokenOrListParam);
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -0,0 +1,133 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.Observation;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
@RunWith(SpringJUnit4ClassRunner.class)
public class FhirResourceDaoR4SearchLastNAsyncIT extends BaseR4SearchLastN {
@Autowired
protected DaoConfig myDaoConfig;
private List<Integer> originalPreFetchThresholds;
@Before
public void before() {
RestfulServer myServer = new RestfulServer(myFhirCtx);
myServer.setPagingProvider(myDatabaseBackedPagingProvider);
when(mySrd.getServer()).thenReturn(myServer);
// Set pre-fetch sizes small so that most tests are forced to do multiple fetches.
// This will allow testing a common use case where result set is larger than first fetch size but smaller than the normal query chunk size.
originalPreFetchThresholds = myDaoConfig.getSearchPreFetchThresholds();
List<Integer> mySmallerPreFetchThresholds = new ArrayList<>();
mySmallerPreFetchThresholds.add(20);
mySmallerPreFetchThresholds.add(400);
mySmallerPreFetchThresholds.add(-1);
myDaoConfig.setSearchPreFetchThresholds(mySmallerPreFetchThresholds);
SearchBuilder.setMaxPageSize50ForTest(true);
}
@After
public void after() {
myDaoConfig.setSearchPreFetchThresholds(originalPreFetchThresholds);
SearchBuilder.setMaxPageSize50ForTest(false);
}
@Test
public void testLastNChunking() {
// Set up search parameters that will return 75 Observations.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam codeParam1 = new TokenParam(codeSystem, observationCd0);
TokenParam codeParam2 = new TokenParam(codeSystem, observationCd1);
params.add(Observation.SP_CODE, buildTokenAndListParam(codeParam1, codeParam2));
params.setLastN(true);
params.setLastNMax(100);
Map<String, String[]> requestParameters = new HashMap<>();
when(mySrd.getParameters()).thenReturn(requestParameters);
// Set chunk size to 50
SearchBuilder.setMaxPageSize50ForTest(true);
// Expand default fetch sizes to ensure all observations are returned in first page:
List<Integer> myBiggerPreFetchThresholds = new ArrayList<>();
myBiggerPreFetchThresholds.add(100);
myBiggerPreFetchThresholds.add(1000);
myBiggerPreFetchThresholds.add(-1);
myDaoConfig.setSearchPreFetchThresholds(myBiggerPreFetchThresholds);
myCaptureQueriesListener.clear();
List<String> results = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null));
assertEquals(75, results.size());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
List<String> queries = myCaptureQueriesListener
.getSelectQueriesForCurrentThread()
.stream()
.map(t -> t.getSql(true, false))
.collect(Collectors.toList());
// 1 query to lookup up Search from cache, and 2 chunked queries to retrieve resources by PID.
assertEquals(3, queries.size());
// The first chunked query should have a full complement of PIDs
StringBuilder firstQueryPattern = new StringBuilder(".*RES_ID in \\('[0-9]+'");
for (int pidIndex = 1; pidIndex<50; pidIndex++) {
firstQueryPattern.append(" , '[0-9]+'");
}
firstQueryPattern.append("\\).*");
assertThat(queries.get(1), matchesPattern(firstQueryPattern.toString()));
// the second chunked query should be padded with "-1".
StringBuilder secondQueryPattern = new StringBuilder(".*RES_ID in \\('[0-9]+'");
for (int pidIndex = 1; pidIndex<25; pidIndex++) {
secondQueryPattern.append(" , '[0-9]+'");
}
for (int pidIndex = 0; pidIndex<25; pidIndex++) {
secondQueryPattern.append(" , '-1'");
}
secondQueryPattern.append("\\).*");
assertThat(queries.get(2), matchesPattern(secondQueryPattern.toString()));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -0,0 +1,92 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.*;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
@RunWith(SpringJUnit4ClassRunner.class)
public class FhirResourceDaoR4SearchLastNIT extends BaseR4SearchLastN {
@After
public void resetMaximumPageSize() {
SearchBuilder.setMaxPageSize50ForTest(false);
}
@Test
public void testLastNChunking() {
// Set up search parameters that will return 75 Observations.
SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam codeParam1 = new TokenParam(codeSystem, observationCd0);
TokenParam codeParam2 = new TokenParam(codeSystem, observationCd1);
params.add(Observation.SP_CODE, buildTokenAndListParam(codeParam1, codeParam2));
params.setLastN(true);
params.setLastNMax(100);
Map<String, String[]> requestParameters = new HashMap<>();
when(mySrd.getParameters()).thenReturn(requestParameters);
// Set chunk size to 50
SearchBuilder.setMaxPageSize50ForTest(true);
myCaptureQueriesListener.clear();
List<String> results = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null));
assertEquals(75, results.size());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
List<String> queries = myCaptureQueriesListener
.getSelectQueriesForCurrentThread()
.stream()
.map(t -> t.getSql(true, false))
.collect(Collectors.toList());
// Two chunked queries executed by the QueryIterator (in current thread) and two chunked queries to retrieve resources by PID.
assertEquals(4, queries.size());
// The first and third chunked queries should have a full complement of PIDs
StringBuilder firstQueryPattern = new StringBuilder(".*RES_ID in \\('[0-9]+'");
for (int pidIndex = 1; pidIndex<50; pidIndex++) {
firstQueryPattern.append(" , '[0-9]+'");
}
firstQueryPattern.append("\\).*");
assertThat(queries.get(0), matchesPattern(firstQueryPattern.toString()));
assertThat(queries.get(2), matchesPattern(firstQueryPattern.toString()));
// the second and fourth chunked queries should be padded with "-1".
StringBuilder secondQueryPattern = new StringBuilder(".*RES_ID in \\('[0-9]+'");
for (int pidIndex = 1; pidIndex<25; pidIndex++) {
secondQueryPattern.append(" , '[0-9]+'");
}
for (int pidIndex = 0; pidIndex<25; pidIndex++) {
secondQueryPattern.append(" , '-1'");
}
secondQueryPattern.append("\\).*");
assertThat(queries.get(1), matchesPattern(secondQueryPattern.toString()));
assertThat(queries.get(3), matchesPattern(secondQueryPattern.toString()));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -530,7 +530,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(DiagnosticReport.SP_PERFORMER, new ReferenceParam( "CareTeam").setChain(PARAM_TYPE));
map.add(DiagnosticReport.SP_PERFORMER, new ReferenceParam("CareTeam").setChain(PARAM_TYPE));
results = myDiagnosticReportDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids.toString(), ids, contains(drId1.getValue()));
@ -1690,6 +1690,338 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
@Test
public void testDateRangeOnPeriod_SearchByDateTime_NoUpperBound() {
Encounter enc = new Encounter();
enc.getPeriod().getStartElement().setValueAsString("2020-05-26T12:00:00Z");
String id1 = myEncounterDao.create(enc).getId().toUnqualifiedVersionless().getValue();
runInTransaction(() -> {
ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
// ge -> above the lower bound
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("ge2020-05-26T13:00:00Z"));
myCaptureQueriesListener.clear();
IBundleProvider results = myEncounterDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// ge -> Below the lower bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("ge2020-05-26T11:00:00Z"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// le -> above the lower bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("le2020-05-26T13:00:00Z"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// le -> Below the lower bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("le2020-05-26T11:00:00Z"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, empty());
}
@Test
public void testDateRangeOnPeriod_SearchByDate_NoUpperBound() {
Encounter enc = new Encounter();
enc.getPeriod().getStartElement().setValueAsString("2020-05-26T12:00:00Z");
String id1 = myEncounterDao.create(enc).getId().toUnqualifiedVersionless().getValue();
runInTransaction(() -> {
ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
// ge -> above the lower bound
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("ge2020-05-27"));
myCaptureQueriesListener.clear();
IBundleProvider results = myEncounterDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// ge -> Below the lower bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("ge2020-05-25"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// le -> above the lower bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("le2020-05-27"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// le -> Below the lower bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("le2020-05-25"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, empty());
}
@Test
public void testDateRangeOnPeriod_SearchByDateTime_NoLowerBound() {
Encounter enc = new Encounter();
enc.getPeriod().getEndElement().setValueAsString("2020-05-26T12:00:00Z");
String id1 = myEncounterDao.create(enc).getId().toUnqualifiedVersionless().getValue();
runInTransaction(() -> {
ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
// le -> above the upper bound
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("le2020-05-26T13:00:00Z"));
myCaptureQueriesListener.clear();
IBundleProvider results = myEncounterDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// le -> Below the upper bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("le2020-05-26T11:00:00Z"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// ge -> above the upper bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("ge2020-05-26T13:00:00Z"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, empty());
// ge -> Below the upper bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("ge2020-05-26T11:00:00Z"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
}
@Test
public void testDateRangeOnPeriod_SearchByDate_NoLowerBound() {
Encounter enc = new Encounter();
enc.getPeriod().getEndElement().setValueAsString("2020-05-26T12:00:00Z");
String id1 = myEncounterDao.create(enc).getId().toUnqualifiedVersionless().getValue();
runInTransaction(() -> {
ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
// le -> above the upper bound
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("le2020-05-27"));
myCaptureQueriesListener.clear();
IBundleProvider results = myEncounterDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// le -> Below the upper bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("le2020-05-25"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
// ge -> above the upper bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("ge2020-05-27"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, empty());
// ge -> Below the upper bound
map = SearchParameterMap.newSynchronous();
map.add(Encounter.SP_DATE, new DateParam("ge2020-05-25"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(id1));
}
@Test
public void testDatePeriodParamEndOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params;
List<Encounter> encs;
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartAndEnd() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("03");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-03");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("01");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
}
/**
* See #1174
*/

View File

@ -311,7 +311,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
* 20 should be prefetched since that's the initial page size
*/
await().until(()-> runInTransaction(()->{
await().until(() -> runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""));
return search.getNumFound() >= 200;
}));
@ -371,8 +371,8 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
* 20 should be prefetched since that's the initial page size
*/
await().until(()->{
return runInTransaction(()->{
await().until(() -> {
return runInTransaction(() -> {
return mySearchEntityDao
.findByUuidAndFetchIncludes(uuid)
.orElseThrow(() -> new InternalErrorException(""))
@ -507,8 +507,8 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
* 20 should be prefetched since that's the initial page size
*/
await().until(()->{
return runInTransaction(()->{
await().until(() -> {
return runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""));
return search.getNumFound() >= 50;
});
@ -547,8 +547,8 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
/*
* 20 should be prefetched since that's the initial page size
*/
await().until(()->{
return runInTransaction(()->{
await().until(() -> {
return runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""));
return search.getNumFound() == 20;
});
@ -611,7 +611,12 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
* 20 should be prefetched since that's the initial page size
*/
waitForSize(20, () -> runInTransaction(() -> mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException("")).getNumFound()));
waitForSize(
20,
10000,
() -> runInTransaction(() -> mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException("")).getNumFound()),
() -> "Wanted 20: " + runInTransaction(() -> mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException("")).toString()));
runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""));
assertEquals(20, search.getNumFound());
@ -673,7 +678,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
assertEquals("Patient/PT00000", ids.get(0));
assertEquals(1, ids.size());
await().until(()-> runInTransaction(()-> mySearchEntityDao
await().until(() -> runInTransaction(() -> mySearchEntityDao
.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""))
.getStatus() == SearchStatusEnum.FINISHED));
@ -821,7 +826,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
search.getResources(0, 20);
ourLog.info("** Done retrieving resources");
await().until(()->myCaptureQueriesListener.countSelectQueries() == 4);
await().until(() -> myCaptureQueriesListener.countSelectQueries() == 4);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(4, myCaptureQueriesListener.countSelectQueries());

View File

@ -60,10 +60,10 @@ import static org.junit.Assert.assertThat;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestR4ConfigWithElasticSearch.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class FhirResourceDaoR4SearchWithElasticSearchTest extends BaseJpaTest {
public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system";
public static final String URL_MY_VALUE_SET = "http://example.com/my_value_set";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchWithElasticSearchTest.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchWithElasticSearchIT.class);
@Autowired
protected DaoConfig myDaoConfig;
@Autowired

View File

@ -1118,143 +1118,6 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
}
}
@Test
public void testDatePeriodParamEndOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params;
List<Encounter> encs;
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
// encs = toList(ourEncounterDao.search(params));
// assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "02"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartAndEnd() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("03");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
enc.getPeriod().getEndElement().setValueAsString("2001-01-03");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "03"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDatePeriodParamStartOnly() {
{
Encounter enc = new Encounter();
enc.addIdentifier().setSystem("testDatePeriodParam").setValue("01");
enc.getPeriod().getStartElement().setValueAsString("2001-01-02");
myEncounterDao.create(enc, mySrd);
}
SearchParameterMap params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
List<Encounter> encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01"));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new TokenParam("testDatePeriodParam", "01"));
encs = toList(myEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testDeleteFailsIfIncomingLinks() {

View File

@ -68,6 +68,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
@ -701,6 +702,60 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
public void testUpdateConditionalInPartition() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
createRequestId();
// Create a resource
addCreatePartition(myPartitionId, myPartitionDate);
addReadPartition(myPartitionId);
Patient p = new Patient();
p.setActive(false);
p.addIdentifier().setValue("12345");
Long patientId = myPatientDao.update(p, "Patient?identifier=12345", mySrd).getId().getIdPartAsLong();
runInTransaction(() -> {
// HFJ_RESOURCE
assertEquals(1, myResourceTableDao.count());
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate());
// HFJ_SPIDX_TOKEN
ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
assertEquals(3, myResourceIndexedSearchParamTokenDao.countForResourceId(patientId));
});
// Update that resource
addReadPartition(myPartitionId);
p = new Patient();
p.setActive(true);
p.addIdentifier().setValue("12345");
Long patientId2 = myPatientDao.update(p, "Patient?identifier=12345", mySrd).getId().getIdPartAsLong();
assertEquals(patientId, patientId2);
runInTransaction(() -> {
// HFJ_RESOURCE
assertEquals(1, myResourceTableDao.count());
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate());
// HFJ_SPIDX_TOKEN
ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
assertEquals(3, myResourceIndexedSearchParamTokenDao.countForResourceId(patientId));
// HFJ_RES_VER
int version = 2;
ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, version);
assertEquals(myPartitionId, resVer.getPartitionId().getPartitionId().intValue());
assertEquals(myPartitionDate, resVer.getPartitionId().getPartitionDate());
});
}
@Test
public void testRead_PidId_AllPartitions() {
IIdType patientId1 = createPatient(withPartition(1), withActiveTrue());

View File

@ -0,0 +1,417 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.TestR4ConfigWithElasticsearchClient;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodeableConceptSearchParamDao;
import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedSearchParamLastNDao;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.param.*;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r4.model.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestR4ConfigWithElasticsearchClient.class})
public class PersistObservationIndexedSearchParamLastNR4IT {
@Autowired
IObservationIndexedSearchParamLastNDao myResourceIndexedObservationLastNDao;
@Autowired
IObservationIndexedCodeCodeableConceptSearchParamDao myCodeableConceptIndexedSearchParamNormalizedDao;
@Autowired
private ElasticsearchSvcImpl elasticsearchSvc;
@Autowired
private IFhirSystemDao<Bundle, Meta> myDao;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
ObservationLastNIndexPersistSvc testObservationPersist;
@Autowired
protected FhirContext myFhirCtx;
@Before
public void before() {
myResourceIndexedObservationLastNDao.deleteAll();
myCodeableConceptIndexedSearchParamNormalizedDao.deleteAll();
}
private final String SINGLE_SUBJECT_ID = "4567";
private final String SINGLE_OBSERVATION_PID = "123";
private final Date SINGLE_EFFECTIVEDTM = new Date();
private final String SINGLE_OBSERVATION_CODE_TEXT = "Test Codeable Concept Field for Code";
private final String CATEGORYFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-category";
private final String FIRSTCATEGORYFIRSTCODINGCODE = "test-heart-rate";
private final String CODEFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-code";
private final String CODEFIRSTCODINGCODE = "test-code";
private ReferenceAndListParam multiSubjectParams = null;
@Test
public void testIndexObservationSingle() {
indexSingleObservation();
List<ObservationIndexedSearchParamLastNEntity> persistedObservationEntities = myResourceIndexedObservationLastNDao.findAll();
assertEquals(1, persistedObservationEntities.size());
ObservationIndexedSearchParamLastNEntity persistedObservationEntity = persistedObservationEntities.get(0);
assertEquals(SINGLE_SUBJECT_ID, persistedObservationEntity.getSubject());
assertEquals(SINGLE_OBSERVATION_PID, persistedObservationEntity.getIdentifier());
assertEquals(SINGLE_EFFECTIVEDTM, persistedObservationEntity.getEffectiveDtm());
String observationCodeNormalizedId = persistedObservationEntity.getCodeNormalizedId();
List<ObservationIndexedCodeCodeableConceptEntity> persistedObservationCodes = myCodeableConceptIndexedSearchParamNormalizedDao.findAll();
assertEquals(1, persistedObservationCodes.size());
ObservationIndexedCodeCodeableConceptEntity persistedObservationCode = persistedObservationCodes.get(0);
assertEquals(observationCodeNormalizedId, persistedObservationCode.getCodeableConceptId());
assertEquals(SINGLE_OBSERVATION_CODE_TEXT, persistedObservationCode.getCodeableConceptText());
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", SINGLE_SUBJECT_ID);
searchParameterMap.add(Observation.SP_SUBJECT, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().addOr(subjectParam)));
TokenParam categoryParam = new TokenParam(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE);
searchParameterMap.add(Observation.SP_CATEGORY, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(categoryParam)));
TokenParam codeParam = new TokenParam(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE);
searchParameterMap.add(Observation.SP_CODE, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(codeParam)));
searchParameterMap.setLastNMax(3);
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 100);
assertEquals(1, observationIdsOnly.size());
assertEquals(SINGLE_OBSERVATION_PID, observationIdsOnly.get(0));
}
private void indexSingleObservation() {
Observation myObservation = new Observation();
IdType observationID = new IdType("Observation", SINGLE_OBSERVATION_PID, "1");
myObservation.setId(observationID);
Reference subjectId = new Reference(SINGLE_SUBJECT_ID);
myObservation.setSubject(subjectId);
myObservation.setEffective(new DateTimeType(SINGLE_EFFECTIVEDTM));
myObservation.setCategory(getCategoryCode());
myObservation.setCode(getObservationCode());
testObservationPersist.indexObservation(myObservation);
}
private List<CodeableConcept> getCategoryCode() {
// Add three CodeableConcepts for category
List<CodeableConcept> categoryConcepts = new ArrayList<>();
// Create three codings and first category CodeableConcept
List<Coding> category = new ArrayList<>();
CodeableConcept categoryCodeableConcept1 = new CodeableConcept().setText("Test Codeable Concept Field for first category");
category.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE, "test-heart-rate display"));
category.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-heart-rate", "test-alt-heart-rate display"));
category.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-heart-rate", "test-2nd-alt-heart-rate display"));
categoryCodeableConcept1.setCoding(category);
categoryConcepts.add(categoryCodeableConcept1);
// Create three codings and second category CodeableConcept
List<Coding> category2 = new ArrayList<>();
CodeableConcept categoryCodeableConcept2 = new CodeableConcept().setText("Test Codeable Concept Field for for second category");
category2.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, "test-vital-signs", "test-vital-signs display"));
category2.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-vitals", "test-alt-vitals display"));
category2.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-vitals", "test-2nd-alt-vitals display"));
categoryCodeableConcept2.setCoding(category2);
categoryConcepts.add(categoryCodeableConcept2);
// Create three codings and third category CodeableConcept
List<Coding> category3 = new ArrayList<>();
CodeableConcept categoryCodeableConcept3 = new CodeableConcept().setText("Test Codeable Concept Field for third category");
category3.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, "test-vitals-panel", "test-vitals-panel display"));
category3.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-vitals-panel", "test-alt-vitals-panel display"));
category3.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-vitals-panel", "test-2nd-alt-vitals-panel display"));
categoryCodeableConcept3.setCoding(category3);
categoryConcepts.add(categoryCodeableConcept3);
return categoryConcepts;
}
private CodeableConcept getObservationCode() {
// Create CodeableConcept for Code with three codings.
CodeableConcept codeableConceptField = new CodeableConcept().setText(SINGLE_OBSERVATION_CODE_TEXT);
codeableConceptField.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE, "test-code display"));
return codeableConceptField;
}
@Test
public void testIndexObservationMultiple() {
indexMultipleObservations();
assertEquals(100, myResourceIndexedObservationLastNDao.count());
assertEquals(2, myCodeableConceptIndexedSearchParamNormalizedDao.count());
// Check that all observations were indexed.
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.add(Observation.SP_SUBJECT, multiSubjectParams);
searchParameterMap.setLastNMax(10);
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
assertEquals(100, observationIdsOnly.size());
// Filter the results by category code.
TokenParam categoryParam = new TokenParam(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE);
searchParameterMap.add(Observation.SP_CATEGORY, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(categoryParam)));
observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 100);
assertEquals(50, observationIdsOnly.size());
}
private void indexMultipleObservations() {
// Create two CodeableConcept values each for a Code with three codings.
CodeableConcept codeableConceptField1 = new CodeableConcept().setText("Test Codeable Concept Field for First Code");
codeableConceptField1.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, "test-code-1", "test-code-1 display"));
codeableConceptField1.addCoding(new Coding("http://myalternatecodes.org/fhir/observation-code", "test-alt-code-1", "test-alt-code-1 display"));
codeableConceptField1.addCoding(new Coding("http://mysecondaltcodes.org/fhir/observation-code", "test-second-alt-code-1", "test-second-alt-code-1 display"));
CodeableConcept codeableConceptField2 = new CodeableConcept().setText("Test Codeable Concept Field for Second Code");
codeableConceptField2.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, "test-code-2", "test-code-2 display"));
codeableConceptField2.addCoding(new Coding("http://myalternatecodes.org/fhir/observation-code", "test-alt-code-2", "test-alt-code-2 display"));
codeableConceptField2.addCoding(new Coding("http://mysecondaltcodes.org/fhir/observation-code", "test-second-alt-code-2", "test-second-alt-code-2 display"));
// Create two CodeableConcept entities for category, each with three codings.
List<Coding> category1 = new ArrayList<>();
// Create three codings and first category CodeableConcept
category1.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE, "test-heart-rate display"));
category1.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-heart-rate", "test-alt-heart-rate display"));
category1.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-heart-rate", "test-2nd-alt-heart-rate display"));
List<CodeableConcept> categoryConcepts1 = new ArrayList<>();
CodeableConcept categoryCodeableConcept1 = new CodeableConcept().setText("Test Codeable Concept Field for first category");
categoryCodeableConcept1.setCoding(category1);
categoryConcepts1.add(categoryCodeableConcept1);
// Create three codings and second category CodeableConcept
List<Coding> category2 = new ArrayList<>();
category2.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, "test-vital-signs", "test-vital-signs display"));
category2.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-vitals", "test-alt-vitals display"));
category2.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-vitals", "test-2nd-alt-vitals display"));
List<CodeableConcept> categoryConcepts2 = new ArrayList<>();
CodeableConcept categoryCodeableConcept2 = new CodeableConcept().setText("Test Codeable Concept Field for second category");
categoryCodeableConcept2.setCoding(category2);
categoryConcepts2.add(categoryCodeableConcept2);
ReferenceOrListParam subjectParams = new ReferenceOrListParam();
for (int patientCount = 0; patientCount < 10; patientCount++) {
String subjectId = String.valueOf(patientCount);
ReferenceParam subjectParam = new ReferenceParam("Patient", "", subjectId);
subjectParams.addOr(subjectParam);
for (int entryCount = 0; entryCount < 10; entryCount++) {
Observation observation = new Observation();
IdType observationId = new IdType("Observation", String.valueOf(entryCount + patientCount * 10), "1");
observation.setId(observationId);
Reference subject = new Reference(subjectId);
observation.setSubject(subject);
if (entryCount % 2 == 1) {
observation.setCategory(categoryConcepts1);
observation.setCode(codeableConceptField1);
} else {
observation.setCategory(categoryConcepts2);
observation.setCode(codeableConceptField2);
}
Calendar observationDate = new GregorianCalendar();
observationDate.add(Calendar.HOUR, -10 + entryCount);
Date effectiveDtm = observationDate.getTime();
observation.setEffective(new DateTimeType(effectiveDtm));
testObservationPersist.indexObservation(observation);
}
}
multiSubjectParams = new ReferenceAndListParam().addAnd(subjectParams);
}
@Test
public void testDeleteObservation() {
indexMultipleObservations();
assertEquals(100, myResourceIndexedObservationLastNDao.count());
// Check that fifth observation for fifth patient has been indexed.
ObservationIndexedSearchParamLastNEntity observation = myResourceIndexedObservationLastNDao.findByIdentifier("55");
assertNotNull(observation);
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.add(Observation.SP_SUBJECT, multiSubjectParams);
searchParameterMap.setLastNMax(10);
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
assertEquals(100, observationIdsOnly.size());
assertTrue(observationIdsOnly.contains("55"));
// Delete fifth observation for fifth patient.
ResourceTable entity = new ResourceTable();
entity.setId(55L);
entity.setResourceType("Observation");
entity.setVersion(0L);
testObservationPersist.deleteObservationIndex(entity);
// Confirm that observation was deleted.
assertEquals(99, myResourceIndexedObservationLastNDao.count());
observation = myResourceIndexedObservationLastNDao.findByIdentifier("55");
assertNull(observation);
observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
assertEquals(99, observationIdsOnly.size());
assertTrue(!observationIdsOnly.contains("55"));
}
@Test
public void testUpdateObservation() {
indexSingleObservation();
ObservationIndexedSearchParamLastNEntity observationIndexEntity = myResourceIndexedObservationLastNDao.findAll().get(0);
assertEquals(SINGLE_OBSERVATION_PID, observationIndexEntity.getIdentifier());
assertEquals(SINGLE_SUBJECT_ID, observationIndexEntity.getSubject());
assertEquals(SINGLE_EFFECTIVEDTM, observationIndexEntity.getEffectiveDtm());
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", SINGLE_SUBJECT_ID);
searchParameterMap.add(Observation.SP_SUBJECT, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().addOr(subjectParam)));
TokenParam categoryParam = new TokenParam(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE);
searchParameterMap.add(Observation.SP_CATEGORY, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(categoryParam)));
TokenParam codeParam = new TokenParam(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE);
searchParameterMap.add(Observation.SP_CODE, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(codeParam)));
searchParameterMap.setLastNMax(10);
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
assertEquals(1, observationIdsOnly.size());
assertTrue(observationIdsOnly.contains(SINGLE_OBSERVATION_PID));
// Update the Observation with a new Subject and effective date:
Observation updatedObservation = new Observation();
IdType observationId = new IdType("Observation", observationIndexEntity.getIdentifier(), "2");
updatedObservation.setId(observationId);
Reference subjectId = new Reference("1234");
updatedObservation.setSubject(subjectId);
DateTimeType newEffectiveDtm = new DateTimeType(new Date());
updatedObservation.setEffective(newEffectiveDtm);
updatedObservation.setCategory(getCategoryCode());
updatedObservation.setCode(getObservationCode());
testObservationPersist.indexObservation(updatedObservation);
ObservationIndexedSearchParamLastNEntity updatedObservationEntity = myResourceIndexedObservationLastNDao.findByIdentifier(SINGLE_OBSERVATION_PID);
assertEquals("1234", updatedObservationEntity.getSubject());
assertEquals(newEffectiveDtm.getValue(), updatedObservationEntity.getEffectiveDtm());
// Repeat earlier Elasticsearch query. This time, should return no matches.
observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
assertEquals(0, observationIdsOnly.size());
// Try again with the new patient ID.
searchParameterMap = new SearchParameterMap();
subjectParam = new ReferenceParam("Patient", "", "1234");
searchParameterMap.add(Observation.SP_SUBJECT, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().addOr(subjectParam)));
searchParameterMap.add(Observation.SP_CATEGORY, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(categoryParam)));
searchParameterMap.add(Observation.SP_CODE, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(codeParam)));
searchParameterMap.setLastNMax(10);
observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
// Should see the observation returned now.
assertEquals(1, observationIdsOnly.size());
assertTrue(observationIdsOnly.contains(SINGLE_OBSERVATION_PID));
}
@Test
public void testSampleBundleInTransaction() throws IOException {
FhirContext myFhirCtx = FhirContext.forR4();
PathMatchingResourcePatternResolver provider = new PathMatchingResourcePatternResolver();
final Resource[] bundleResources;
bundleResources = provider.getResources("lastntestbundle.json");
AtomicInteger index = new AtomicInteger();
Arrays.stream(bundleResources).forEach(
resource -> {
index.incrementAndGet();
InputStream resIs = null;
String nextBundleString;
try {
resIs = resource.getInputStream();
nextBundleString = IOUtils.toString(resIs, Charsets.UTF_8);
} catch (IOException e) {
return;
} finally {
try {
if (resIs != null) {
resIs.close();
}
} catch (final IOException ioe) {
// ignore
}
}
IParser parser = myFhirCtx.newJsonParser();
Bundle bundle = parser.parseResource(Bundle.class, nextBundleString);
myDao.transaction(null, bundle);
}
);
SearchParameterMap searchParameterMap = new SearchParameterMap();
// execute Observation ID search - Composite Aggregation
searchParameterMap.setLastNMax(1);
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap,myFhirCtx, 200);
assertEquals(20, observationIdsOnly.size());
searchParameterMap.setLastNMax(3);
observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
assertEquals(38, observationIdsOnly.size());
}
}

View File

@ -0,0 +1,419 @@
package ca.uhn.fhir.jpa.search.lastn;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.jpa.search.lastn.config.TestElasticsearchConfig;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.hl7.fhir.r4.model.Observation;
import org.junit.*;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.IOException;
import java.util.*;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestElasticsearchConfig.class})
public class LastNElasticsearchSvcMultipleObservationsIT {
@Autowired
private ElasticsearchSvcImpl elasticsearchSvc;
private static ObjectMapper ourMapperNonPrettyPrint;
private final Map<String, Map<String, List<Date>>> createdPatientObservationMap = new HashMap<>();
private FhirContext myFhirContext = FhirContext.forR4();
@BeforeClass
public static void beforeClass() {
ourMapperNonPrettyPrint = new ObjectMapper();
ourMapperNonPrettyPrint.setSerializationInclusion(JsonInclude.Include.NON_NULL);
ourMapperNonPrettyPrint.disable(SerializationFeature.INDENT_OUTPUT);
ourMapperNonPrettyPrint.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
}
@Before
public void before() throws IOException {
createMultiplePatientsAndObservations();
}
@After
public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
@Test
public void testLastNAllPatientsQuery() {
// execute Observation ID search (Composite Aggregation) last 3 observations for each patient
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", "0");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "1");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "2");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "3");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "4");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "5");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "6");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "7");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "8");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "9");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
searchParameterMap.setLastNMax(3);
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(searchParameterMap, myFhirContext);
assertEquals(60, observations.size());
// Observation documents should be grouped by subject, then by observation code, and then sorted by effective date/time
// within each observation code. Verify the grouping by creating a nested Map.
Map<String, Map<String, List<Date>>> queriedPatientObservationMap = new HashMap<>();
ObservationJson previousObservationJson = null;
for (ObservationJson observationJson : observations) {
assertNotNull(observationJson.getIdentifier());
assertNotNull(observationJson.getSubject());
assertNotNull(observationJson.getCode_concept_id());
assertNotNull(observationJson.getEffectiveDtm());
if (previousObservationJson == null) {
ArrayList<Date> observationDates = new ArrayList<>();
observationDates.add(observationJson.getEffectiveDtm());
Map<String, List<Date>> codeObservationMap = new HashMap<>();
codeObservationMap.put(observationJson.getCode_concept_id(), observationDates);
queriedPatientObservationMap.put(observationJson.getSubject(), codeObservationMap);
} else if (observationJson.getSubject().equals(previousObservationJson.getSubject())) {
if (observationJson.getCode_concept_id().equals(previousObservationJson.getCode_concept_id())) {
queriedPatientObservationMap.get(observationJson.getSubject()).get(observationJson.getCode_concept_id()).
add(observationJson.getEffectiveDtm());
} else {
Map<String, List<Date>> codeObservationDateMap = queriedPatientObservationMap.get(observationJson.getSubject());
// Ensure that code concept was not already retrieved out of order for this subject/patient.
assertFalse(codeObservationDateMap.containsKey(observationJson.getCode_concept_id()));
ArrayList<Date> observationDates = new ArrayList<>();
observationDates.add(observationJson.getEffectiveDtm());
codeObservationDateMap.put(observationJson.getCode_concept_id(), observationDates);
}
} else {
// Ensure that subject/patient was not already retrieved out of order
assertFalse(queriedPatientObservationMap.containsKey(observationJson.getSubject()));
ArrayList<Date> observationDates = new ArrayList<>();
observationDates.add(observationJson.getEffectiveDtm());
Map<String, List<Date>> codeObservationMap = new HashMap<>();
codeObservationMap.put(observationJson.getCode_concept_id(), observationDates);
queriedPatientObservationMap.put(observationJson.getSubject(), codeObservationMap);
}
previousObservationJson = observationJson;
}
// Finally check that only the most recent effective date/time values were returned and in the correct order.
for (String subjectId : queriedPatientObservationMap.keySet()) {
Map<String, List<Date>> queriedObservationCodeMap = queriedPatientObservationMap.get(subjectId);
Map<String, List<Date>> createdObservationCodeMap = createdPatientObservationMap.get(subjectId);
for (String observationCode : queriedObservationCodeMap.keySet()) {
List<Date> queriedObservationDates = queriedObservationCodeMap.get(observationCode);
List<Date> createdObservationDates = createdObservationCodeMap.get(observationCode);
for (int dateIdx = 0; dateIdx < queriedObservationDates.size(); dateIdx++) {
assertEquals(createdObservationDates.get(dateIdx), queriedObservationDates.get(dateIdx));
}
}
}
}
@Test
public void testLastNMultiPatientMultiCodeHashMultiCategoryHash() {
// Multiple Subject references
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", "3");
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", "5");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2));
TokenParam categoryParam1 = new TokenParam("http://mycodes.org/fhir/observation-category", "test-heart-rate");
TokenParam categoryParam2 = new TokenParam("http://mycodes.org/fhir/observation-category", "test-vital-signs");
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam1, categoryParam2));
TokenParam codeParam1 = new TokenParam("http://mycodes.org/fhir/observation-code", "test-code-1");
TokenParam codeParam2 = new TokenParam("http://mycodes.org/fhir/observation-code", "test-code-2");
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam1, codeParam2));
searchParameterMap.setLastNMax(100);
List<String> observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(20, observations.size());
// Repeat with multiple Patient parameter
searchParameterMap = new SearchParameterMap();
ReferenceParam patientParam1 = new ReferenceParam("Patient", "", "8");
ReferenceParam patientParam2 = new ReferenceParam("Patient", "", "6");
searchParameterMap.add(Observation.SP_PATIENT, buildReferenceAndListParam(patientParam1, patientParam2));
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam1, categoryParam2));
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam1, codeParam2));
searchParameterMap.setLastNMax(100);
observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(20, observations.size());
}
private ReferenceAndListParam buildReferenceAndListParam(ReferenceParam... theReference) {
ReferenceOrListParam myReferenceOrListParam = new ReferenceOrListParam();
for (ReferenceParam referenceParam : theReference) {
myReferenceOrListParam.addOr(referenceParam);
}
return new ReferenceAndListParam().addAnd(myReferenceOrListParam);
}
private TokenAndListParam buildTokenAndListParam(TokenParam... theToken) {
TokenOrListParam myTokenOrListParam = new TokenOrListParam();
for (TokenParam tokenParam : theToken) {
myTokenOrListParam.addOr(tokenParam);
}
return new TokenAndListParam().addAnd(myTokenOrListParam);
}
@Test
public void testLastNCodeCodeOnlyCategoryCodeOnly() {
// Include subject
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", "3");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
TokenParam categoryParam = new TokenParam("test-heart-rate");
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
TokenParam codeParam = new TokenParam("test-code-1");
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam));
searchParameterMap.setLastNMax(100);
List<String> observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(5, observations.size());
}
@Test
public void testLastNCodeSystemOnlyCategorySystemOnly() {
// Include subject and patient
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", "3");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
TokenParam categoryParam = new TokenParam("http://mycodes.org/fhir/observation-category", null);
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
TokenParam codeParam = new TokenParam("http://mycodes.org/fhir/observation-code", null);
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam));
searchParameterMap.setLastNMax(100);
List<String> observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(10, observations.size());
}
@Test
public void testLastNCodeCodeTextCategoryTextOnly() {
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", "3");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
TokenParam categoryParam = new TokenParam("test-heart-rate display");
categoryParam.setModifier(TokenParamModifier.TEXT);
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
TokenParam codeParam = new TokenParam("test-code-1 display");
codeParam.setModifier(TokenParamModifier.TEXT);
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam));
searchParameterMap.setLastNMax(100);
List<String> observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(5, observations.size());
}
@Test
public void testLastNNoMatchQueries() {
// Invalid Patient
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam patientParam = new ReferenceParam("Patient", "", "10");
searchParameterMap.add(Observation.SP_PATIENT, buildReferenceAndListParam(patientParam));
TokenParam categoryParam = new TokenParam("http://mycodes.org/fhir/observation-category", "test-heart-rate");
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
TokenParam codeParam = new TokenParam("http://mycodes.org/fhir/observation-code", "test-code-1");
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam));
searchParameterMap.setLastNMax(100);
List<String> observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(0, observations.size());
// Invalid subject
searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", "10");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
categoryParam = new TokenParam("http://mycodes.org/fhir/observation-category", "test-heart-rate");
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
codeParam = new TokenParam("http://mycodes.org/fhir/observation-code", "test-code-1");
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam));
searchParameterMap.setLastNMax(100);
observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(0, observations.size());
// Invalid observation code
searchParameterMap = new SearchParameterMap();
subjectParam = new ReferenceParam("Patient", "", "9");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
categoryParam = new TokenParam("http://mycodes.org/fhir/observation-category", "test-heart-rate");
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
codeParam = new TokenParam("http://mycodes.org/fhir/observation-code", "test-code-999");
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam));
searchParameterMap.setLastNMax(100);
observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(0, observations.size());
// Invalid category code
searchParameterMap = new SearchParameterMap();
subjectParam = new ReferenceParam("Patient", "", "9");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
categoryParam = new TokenParam("http://mycodes.org/fhir/observation-category", "test-not-a-category");
searchParameterMap.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
codeParam = new TokenParam("http://mycodes.org/fhir/observation-code", "test-code-1");
searchParameterMap.add(Observation.SP_CODE, buildTokenAndListParam(codeParam));
searchParameterMap.setLastNMax(100);
observations = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(0, observations.size());
}
private void createMultiplePatientsAndObservations() throws IOException {
// Create CodeableConcepts for two Codes, each with three codings.
String codeableConceptId1 = UUID.randomUUID().toString();
CodeableConcept codeableConceptField1 = new CodeableConcept().setText("Test Codeable Concept Field for First Code");
codeableConceptField1.addCoding(new Coding("http://mycodes.org/fhir/observation-code", "test-code-1", "test-code-1 display"));
CodeJson codeJson1 = new CodeJson(codeableConceptField1, codeableConceptId1);
String codeJson1Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson1);
String codeableConceptId2 = UUID.randomUUID().toString();
CodeableConcept codeableConceptField2 = new CodeableConcept().setText("Test Codeable Concept Field for Second Code");
codeableConceptField2.addCoding(new Coding("http://mycodes.org/fhir/observation-code", "test-code-2", "test-code-2 display"));
CodeJson codeJson2 = new CodeJson(codeableConceptField2, codeableConceptId2);
String codeJson2Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson2);
// Create CodeableConcepts for two categories, each with three codings.
List<Coding> category1 = new ArrayList<>();
// Create three codings and first category CodeableConcept
category1.add(new Coding("http://mycodes.org/fhir/observation-category", "test-heart-rate", "test-heart-rate display"));
category1.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-heart-rate", "test-alt-heart-rate display"));
category1.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-heart-rate", "test-2nd-alt-heart-rate display"));
List<CodeableConcept> categoryConcepts1 = new ArrayList<>();
CodeableConcept categoryCodeableConcept1 = new CodeableConcept().setText("Test Codeable Concept Field for first category");
categoryCodeableConcept1.setCoding(category1);
categoryConcepts1.add(categoryCodeableConcept1);
// Create three codings and second category CodeableConcept
List<Coding> category2 = new ArrayList<>();
category2.add(new Coding("http://mycodes.org/fhir/observation-category", "test-vital-signs", "test-vital-signs display"));
category2.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-vitals", "test-alt-vitals display"));
category2.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-vitals", "test-2nd-alt-vitals display"));
List<CodeableConcept> categoryConcepts2 = new ArrayList<>();
CodeableConcept categoryCodeableConcept2 = new CodeableConcept().setText("Test Codeable Concept Field for second category");
categoryCodeableConcept2.setCoding(category2);
categoryConcepts2.add(categoryCodeableConcept2);
for (int patientCount = 0; patientCount < 10; patientCount++) {
String subject = String.valueOf(patientCount);
for (int entryCount = 0; entryCount < 10; entryCount++) {
ObservationJson observationJson = new ObservationJson();
String identifier = String.valueOf((entryCount + patientCount * 10));
observationJson.setIdentifier(identifier);
observationJson.setSubject(subject);
if (entryCount % 2 == 1) {
observationJson.setCategories(categoryConcepts1);
observationJson.setCode(codeableConceptField1);
observationJson.setCode_concept_id(codeableConceptId1);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, codeableConceptId1, codeJson1Document, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
} else {
observationJson.setCategories(categoryConcepts2);
observationJson.setCode(codeableConceptField2);
observationJson.setCode_concept_id(codeableConceptId2);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, codeableConceptId2, codeJson2Document, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
}
Calendar observationDate = new GregorianCalendar();
observationDate.add(Calendar.HOUR, -10 + entryCount);
Date effectiveDtm = observationDate.getTime();
observationJson.setEffectiveDtm(effectiveDtm);
String observationDocument = ourMapperNonPrettyPrint.writeValueAsString(observationJson);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX, identifier, observationDocument, ElasticsearchSvcImpl.OBSERVATION_DOCUMENT_TYPE));
if (createdPatientObservationMap.containsKey(subject)) {
Map<String, List<Date>> observationCodeMap = createdPatientObservationMap.get(subject);
if (observationCodeMap.containsKey(observationJson.getCode_concept_id())) {
List<Date> observationDates = observationCodeMap.get(observationJson.getCode_concept_id());
// Want dates to be sorted in descending order
observationDates.add(0, effectiveDtm);
// Only keep the three most recent dates for later check.
if (observationDates.size() > 3) {
observationDates.remove(3);
}
} else {
ArrayList<Date> observationDates = new ArrayList<>();
observationDates.add(effectiveDtm);
observationCodeMap.put(observationJson.getCode_concept_id(), observationDates);
}
} else {
ArrayList<Date> observationDates = new ArrayList<>();
observationDates.add(effectiveDtm);
Map<String, List<Date>> codeObservationMap = new HashMap<>();
codeObservationMap.put(observationJson.getCode_concept_id(), observationDates);
createdPatientObservationMap.put(subject, codeObservationMap);
}
}
}
try {
Thread.sleep(2000L);
} catch (InterruptedException theE) {
theE.printStackTrace();
}
}
@Test
public void testLastNNoParamsQuery() {
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.setLastNMax(1);
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(searchParameterMap, myFhirContext);
assertEquals(2, observations.size());
String observationCode1 = observations.get(0).getCode_coding_code_system_hash();
String observationCode2 = observations.get(1).getCode_coding_code_system_hash();
assertNotEquals(observationCode1, observationCode2);
}
}

View File

@ -0,0 +1,312 @@
package ca.uhn.fhir.jpa.search.lastn;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import ca.uhn.fhir.jpa.search.lastn.config.TestElasticsearchConfig;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestElasticsearchConfig.class})
public class LastNElasticsearchSvcSingleObservationIT {
@Autowired
ElasticsearchSvcImpl elasticsearchSvc;
static ObjectMapper ourMapperNonPrettyPrint;
final String RESOURCEPID = "123";
final String SUBJECTID = "Patient/4567";
final Date EFFECTIVEDTM = new Date();
final String FIRSTCATEGORYTEXT = "Test Codeable Concept Field for first category";
final String CATEGORYFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-category";
final String CATEGORYSECONDCODINGSYSTEM = "http://myalternatecodes.org/fhir/observation-category";
final String CATEGORYTHIRDCODINGSYSTEM = "http://mysecondaltcodes.org/fhir/observation-category";
final String FIRSTCATEGORYFIRSTCODINGCODE = "test-heart-rate";
final String FIRSTCATEGORYFIRSTCODINGDISPLAY = "test-heart-rate display";
final String FIRSTCATEGORYSECONDCODINGCODE = "test-alt-heart-rate";
final String FIRSTCATEGORYSECONDCODINGDISPLAY = "test-alt-heart-rate display";
final String FIRSTCATEGORYTHIRDCODINGCODE = "test-2nd-alt-heart-rate";
final String FIRSTCATEGORYTHIRDCODINGDISPLAY = "test-2nd-alt-heart-rate display";
final String SECONDCATEGORYTEXT = "Test Codeable Concept Field for for second category";
final String SECONDCATEGORYFIRSTCODINGCODE = "test-vital-signs";
final String SECONDCATEGORYFIRSTCODINGDISPLAY = "test-vital-signs display";
final String SECONDCATEGORYSECONDCODINGCODE = "test-alt-vitals";
final String SECONDCATEGORYSECONDCODINGDISPLAY = "test-alt-vitals display";
final String SECONDCATEGORYTHIRDCODINGCODE = "test-2nd-alt-vitals";
final String SECONDCATEGORYTHIRDCODINGDISPLAY = "test-2nd-alt-vitals display";
final String THIRDCATEGORYTEXT = "Test Codeable Concept Field for third category";
final String THIRDCATEGORYFIRSTCODINGCODE = "test-vital-panel";
final String THIRDCATEGORYFIRSTCODINGDISPLAY = "test-vitals-panel display";
final String THIRDCATEGORYSECONDCODINGCODE = "test-alt-vitals-panel";
final String THIRDCATEGORYSECONDCODINGDISPLAY = "test-alt-vitals display";
final String THIRDCATEGORYTHIRDCODINGCODE = "test-2nd-alt-vitals-panel";
final String THIRDCATEGORYTHIRDCODINGDISPLAY = "test-2nd-alt-vitals-panel display";
final String OBSERVATIONSINGLECODEID = UUID.randomUUID().toString();
final String OBSERVATIONCODETEXT = "Test Codeable Concept Field for Code";
final String CODEFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-code";
final String CODEFIRSTCODINGCODE = "test-code";
final String CODEFIRSTCODINGDISPLAY = "test-code display";
final FhirContext myFhirContext = FhirContext.forR4();
@BeforeClass
public static void beforeClass() {
ourMapperNonPrettyPrint = new ObjectMapper();
ourMapperNonPrettyPrint.setSerializationInclusion(JsonInclude.Include.NON_NULL);
ourMapperNonPrettyPrint.disable(SerializationFeature.INDENT_OUTPUT);
ourMapperNonPrettyPrint.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
}
@After
public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
@Test
public void testSingleObservationQuery() throws IOException {
createSingleObservation();
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", SUBJECTID);
searchParameterMap.add(Observation.SP_SUBJECT, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().addOr(subjectParam)));
TokenParam categoryParam = new TokenParam(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE);
searchParameterMap.add(Observation.SP_CATEGORY, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(categoryParam)));
TokenParam codeParam = new TokenParam(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE);
searchParameterMap.add(Observation.SP_CODE, new TokenAndListParam().addAnd(new TokenOrListParam().addOr(codeParam)));
searchParameterMap.setLastNMax(3);
// execute Observation ID search
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirContext, 100);
assertEquals(1, observationIdsOnly.size());
assertEquals(RESOURCEPID, observationIdsOnly.get(0));
// execute Observation search for all search fields
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(searchParameterMap, myFhirContext);
validateFullObservationSearch(observations);
}
private void validateFullObservationSearch(List<ObservationJson> observations) throws IOException {
assertEquals(1, observations.size());
ObservationJson observation = observations.get(0);
assertEquals(RESOURCEPID, observation.getIdentifier());
assertEquals(SUBJECTID, observation.getSubject());
assertEquals(RESOURCEPID, observation.getIdentifier());
assertEquals(EFFECTIVEDTM, observation.getEffectiveDtm());
assertEquals(OBSERVATIONSINGLECODEID, observation.getCode_concept_id());
List<String> category_concept_text_values = observation.getCategory_concept_text();
assertEquals(3, category_concept_text_values.size());
assertEquals(FIRSTCATEGORYTEXT, category_concept_text_values.get(0));
assertEquals(SECONDCATEGORYTEXT, category_concept_text_values.get(1));
assertEquals(THIRDCATEGORYTEXT, category_concept_text_values.get(2));
List<List<String>> category_codings_systems = observation.getCategory_coding_system();
assertEquals(3, category_codings_systems.size());
List<String> category_coding_systems = category_codings_systems.get(0);
assertEquals(3, category_coding_systems.size());
assertEquals(CATEGORYFIRSTCODINGSYSTEM, category_coding_systems.get(0));
assertEquals(CATEGORYSECONDCODINGSYSTEM, category_coding_systems.get(1));
assertEquals(CATEGORYTHIRDCODINGSYSTEM, category_coding_systems.get(2));
category_coding_systems = category_codings_systems.get(1);
assertEquals(3, category_coding_systems.size());
assertEquals(CATEGORYFIRSTCODINGSYSTEM, category_coding_systems.get(0));
assertEquals(CATEGORYSECONDCODINGSYSTEM, category_coding_systems.get(1));
assertEquals(CATEGORYTHIRDCODINGSYSTEM, category_coding_systems.get(2));
category_coding_systems = category_codings_systems.get(2);
assertEquals(3, category_coding_systems.size());
assertEquals(CATEGORYFIRSTCODINGSYSTEM, category_coding_systems.get(0));
assertEquals(CATEGORYSECONDCODINGSYSTEM, category_coding_systems.get(1));
assertEquals(CATEGORYTHIRDCODINGSYSTEM, category_coding_systems.get(2));
List<List<String>> category_codings_codes = observation.getCategory_coding_code();
assertEquals(3, category_codings_codes.size());
List<String> category_coding_codes = category_codings_codes.get(0);
assertEquals(3, category_coding_codes.size());
assertEquals(FIRSTCATEGORYFIRSTCODINGCODE, category_coding_codes.get(0));
assertEquals(FIRSTCATEGORYSECONDCODINGCODE, category_coding_codes.get(1));
assertEquals(FIRSTCATEGORYTHIRDCODINGCODE, category_coding_codes.get(2));
category_coding_codes = category_codings_codes.get(1);
assertEquals(3, category_coding_codes.size());
assertEquals(SECONDCATEGORYFIRSTCODINGCODE, category_coding_codes.get(0));
assertEquals(SECONDCATEGORYSECONDCODINGCODE, category_coding_codes.get(1));
assertEquals(SECONDCATEGORYTHIRDCODINGCODE, category_coding_codes.get(2));
category_coding_codes = category_codings_codes.get(2);
assertEquals(3, category_coding_codes.size());
assertEquals(THIRDCATEGORYFIRSTCODINGCODE, category_coding_codes.get(0));
assertEquals(THIRDCATEGORYSECONDCODINGCODE, category_coding_codes.get(1));
assertEquals(THIRDCATEGORYTHIRDCODINGCODE, category_coding_codes.get(2));
List<List<String>> category_codings_displays = observation.getCategory_coding_display();
assertEquals(3, category_codings_displays.size());
List<String> category_coding_displays = category_codings_displays.get(0);
assertEquals(FIRSTCATEGORYFIRSTCODINGDISPLAY, category_coding_displays.get(0));
assertEquals(FIRSTCATEGORYSECONDCODINGDISPLAY, category_coding_displays.get(1));
assertEquals(FIRSTCATEGORYTHIRDCODINGDISPLAY, category_coding_displays.get(2));
category_coding_displays = category_codings_displays.get(1);
assertEquals(3, category_coding_displays.size());
assertEquals(SECONDCATEGORYFIRSTCODINGDISPLAY, category_coding_displays.get(0));
assertEquals(SECONDCATEGORYSECONDCODINGDISPLAY, category_coding_displays.get(1));
assertEquals(SECONDCATEGORYTHIRDCODINGDISPLAY, category_coding_displays.get(2));
category_coding_displays = category_codings_displays.get(2);
assertEquals(3, category_coding_displays.size());
assertEquals(THIRDCATEGORYFIRSTCODINGDISPLAY, category_coding_displays.get(0));
assertEquals(THIRDCATEGORYSECONDCODINGDISPLAY, category_coding_displays.get(1));
assertEquals(THIRDCATEGORYTHIRDCODINGDISPLAY, category_coding_displays.get(2));
List<List<String>> category_codings_code_system_hashes = observation.getCategory_coding_code_system_hash();
assertEquals(3, category_codings_code_system_hashes.size());
List<String> category_coding_code_system_hashes = category_codings_code_system_hashes.get(0);
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE)), category_coding_code_system_hashes.get(0));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYSECONDCODINGSYSTEM, FIRSTCATEGORYSECONDCODINGCODE)), category_coding_code_system_hashes.get(1));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYTHIRDCODINGSYSTEM, FIRSTCATEGORYTHIRDCODINGCODE)), category_coding_code_system_hashes.get(2));
category_coding_code_system_hashes = category_codings_code_system_hashes.get(1);
assertEquals(3, category_coding_code_system_hashes.size());
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYFIRSTCODINGSYSTEM, SECONDCATEGORYFIRSTCODINGCODE)), category_coding_code_system_hashes.get(0));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYSECONDCODINGSYSTEM, SECONDCATEGORYSECONDCODINGCODE)), category_coding_code_system_hashes.get(1));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYTHIRDCODINGSYSTEM, SECONDCATEGORYTHIRDCODINGCODE)), category_coding_code_system_hashes.get(2));
category_coding_code_system_hashes = category_codings_code_system_hashes.get(2);
assertEquals(3, category_coding_code_system_hashes.size());
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYFIRSTCODINGSYSTEM, THIRDCATEGORYFIRSTCODINGCODE)), category_coding_code_system_hashes.get(0));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYSECONDCODINGSYSTEM, THIRDCATEGORYSECONDCODINGCODE)), category_coding_code_system_hashes.get(1));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CATEGORYTHIRDCODINGSYSTEM, THIRDCATEGORYTHIRDCODINGCODE)), category_coding_code_system_hashes.get(2));
String code_concept_text_values = observation.getCode_concept_text();
assertEquals(OBSERVATIONCODETEXT, code_concept_text_values);
String code_coding_systems = observation.getCode_coding_system();
assertEquals(CODEFIRSTCODINGSYSTEM, code_coding_systems);
String code_coding_codes = observation.getCode_coding_code();
assertEquals(CODEFIRSTCODINGCODE, code_coding_codes);
String code_coding_display = observation.getCode_coding_display();
assertEquals(CODEFIRSTCODINGDISPLAY, code_coding_display);
String code_coding_code_system_hash = observation.getCode_coding_code_system_hash();
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE)), code_coding_code_system_hash);
// Retrieve all Observation codes
List<CodeJson> codes = elasticsearchSvc.queryAllIndexedObservationCodes();
assertEquals(1, codes.size());
CodeJson persistedObservationCode = codes.get(0);
String persistedCodeConceptID = persistedObservationCode.getCodeableConceptId();
assertEquals(OBSERVATIONSINGLECODEID, persistedCodeConceptID);
String persistedCodeConceptText = persistedObservationCode.getCodeableConceptText();
assertEquals(OBSERVATIONCODETEXT, persistedCodeConceptText);
List<String> persistedCodeCodingSystems = persistedObservationCode.getCoding_system();
assertEquals(1, persistedCodeCodingSystems.size());
assertEquals(CODEFIRSTCODINGSYSTEM, persistedCodeCodingSystems.get(0));
List<String> persistedCodeCodingCodes = persistedObservationCode.getCoding_code();
assertEquals(1, persistedCodeCodingCodes.size());
assertEquals(CODEFIRSTCODINGCODE, persistedCodeCodingCodes.get(0));
List<String> persistedCodeCodingDisplays = persistedObservationCode.getCoding_display();
assertEquals(1, persistedCodeCodingDisplays.size());
assertEquals(CODEFIRSTCODINGDISPLAY, persistedCodeCodingDisplays.get(0));
List<String> persistedCodeCodingCodeSystemHashes = persistedObservationCode.getCoding_code_system_hash();
assertEquals(1, persistedCodeCodingCodeSystemHashes.size());
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE)), persistedCodeCodingCodeSystemHashes.get(0));
}
private void createSingleObservation() throws IOException {
ObservationJson indexedObservation = new ObservationJson();
indexedObservation.setIdentifier(RESOURCEPID);
indexedObservation.setSubject(SUBJECTID);
indexedObservation.setEffectiveDtm(EFFECTIVEDTM);
// Add three CodeableConcepts for category
List<CodeableConcept> categoryConcepts = new ArrayList<>();
// Create three codings and first category CodeableConcept
List<Coding> category1 = new ArrayList<>();
CodeableConcept categoryCodeableConcept1 = new CodeableConcept().setText(FIRSTCATEGORYTEXT);
category1.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE, FIRSTCATEGORYFIRSTCODINGDISPLAY));
category1.add(new Coding(CATEGORYSECONDCODINGSYSTEM, FIRSTCATEGORYSECONDCODINGCODE, FIRSTCATEGORYSECONDCODINGDISPLAY));
category1.add(new Coding(CATEGORYTHIRDCODINGSYSTEM, FIRSTCATEGORYTHIRDCODINGCODE, FIRSTCATEGORYTHIRDCODINGDISPLAY));
categoryCodeableConcept1.setCoding(category1);
categoryConcepts.add(categoryCodeableConcept1);
// Create three codings and second category CodeableConcept
List<Coding> category2 = new ArrayList<>();
CodeableConcept categoryCodeableConcept2 = new CodeableConcept().setText(SECONDCATEGORYTEXT);
category2.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, SECONDCATEGORYFIRSTCODINGCODE, SECONDCATEGORYFIRSTCODINGDISPLAY));
category2.add(new Coding(CATEGORYSECONDCODINGSYSTEM, SECONDCATEGORYSECONDCODINGCODE, SECONDCATEGORYSECONDCODINGDISPLAY));
category2.add(new Coding(CATEGORYTHIRDCODINGSYSTEM, SECONDCATEGORYTHIRDCODINGCODE, SECONDCATEGORYTHIRDCODINGDISPLAY));
categoryCodeableConcept2.setCoding(category2);
categoryConcepts.add(categoryCodeableConcept2);
// Create three codings and third category CodeableConcept
List<Coding> category3 = new ArrayList<>();
CodeableConcept categoryCodeableConcept3 = new CodeableConcept().setText(THIRDCATEGORYTEXT);
category3.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, THIRDCATEGORYFIRSTCODINGCODE, THIRDCATEGORYFIRSTCODINGDISPLAY));
category3.add(new Coding(CATEGORYSECONDCODINGSYSTEM, THIRDCATEGORYSECONDCODINGCODE, THIRDCATEGORYSECONDCODINGDISPLAY));
category3.add(new Coding(CATEGORYTHIRDCODINGSYSTEM, THIRDCATEGORYTHIRDCODINGCODE, THIRDCATEGORYTHIRDCODINGDISPLAY));
categoryCodeableConcept3.setCoding(category3);
categoryConcepts.add(categoryCodeableConcept3);
indexedObservation.setCategories(categoryConcepts);
// Create CodeableConcept for Code with three codings.
indexedObservation.setCode_concept_id(OBSERVATIONSINGLECODEID);
CodeableConcept codeableConceptField = new CodeableConcept().setText(OBSERVATIONCODETEXT);
codeableConceptField.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE, CODEFIRSTCODINGDISPLAY));
indexedObservation.setCode(codeableConceptField);
String observationDocument = ourMapperNonPrettyPrint.writeValueAsString(indexedObservation);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX, RESOURCEPID, observationDocument, ElasticsearchSvcImpl.OBSERVATION_DOCUMENT_TYPE));
CodeJson observationCode = new CodeJson(codeableConceptField, OBSERVATIONSINGLECODEID);
String codeDocument = ourMapperNonPrettyPrint.writeValueAsString(observationCode);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, OBSERVATIONSINGLECODEID, codeDocument, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
try {
Thread.sleep(1000L);
} catch (InterruptedException theE) {
theE.printStackTrace();
}
}
}

View File

@ -0,0 +1,50 @@
package ca.uhn.fhir.jpa.search.lastn.config;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
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 javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Configuration
public class TestElasticsearchConfig {
private final String elasticsearchHost = "localhost";
private final String elasticsearchUserId = "";
private final String elasticsearchPassword = "";
private static final String ELASTIC_VERSION = "6.5.4";
@Bean()
public ElasticsearchSvcImpl myElasticsearchSvc() throws IOException {
int elasticsearchPort = embeddedElasticSearch().getHttpPort();
return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUserId, elasticsearchPassword);
}
@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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -104,9 +104,6 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
empiLink.addIndex("20200517.5", "IDX_EMPI_PERSON_TGT").unique(true).withColumns("PERSON_PID", "TARGET_PID");
// TRM_CONCEPT_PROPERTY
// version.onTable("TRM_CONCEPT_PROPERTY").addIndex("20200523.1", "IDX_CONCEPTPROP_CONCEPTPID").unique(false).withColumns("CONCEPT_PID");
}
protected void init500() { // 20200218 - 20200519

View File

@ -20,13 +20,17 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu2.model.Subscription;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.DateTimeType;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@ -50,6 +54,21 @@ public class ModelConfig {
"http://hl7.org/fhir/StructureDefinition/*")));
public static final String DEFAULT_WEBSOCKET_CONTEXT_PATH = "/websocket";
/*
* <p>
* Note the following database documented limitations:
* <ul>
* <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
* <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
* <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
* <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
* <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
* </ul>
* </p>
*/
protected static final String DEFAULT_PERIOD_INDEX_START_OF_TIME = "1001-01-01";
protected static final String DEFAULT_PERIOD_INDEX_END_OF_TIME = "9000-01-01";
/**
* update setter javadoc if default changes
*/
@ -67,11 +86,15 @@ public class ModelConfig {
private boolean myUseOrdinalDatesForDayPrecisionSearches = true;
private boolean mySuppressStringIndexingInTokens = false;
private IPrimitiveType<Date> myPeriodIndexStartOfTime;
private IPrimitiveType<Date> myPeriodIndexEndOfTime;
/**
* Constructor
*/
public ModelConfig() {
super();
setPeriodIndexStartOfTime(new DateTimeType(DEFAULT_PERIOD_INDEX_START_OF_TIME));
setPeriodIndexEndOfTime(new DateTimeType(DEFAULT_PERIOD_INDEX_END_OF_TIME));
}
/**
@ -373,8 +396,8 @@ public class ModelConfig {
/**
* <p>
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
* {@link ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link TemporalPrecisionEnum#DAY}.
* <p>
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
* integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
@ -392,8 +415,8 @@ public class ModelConfig {
/**
* <p>
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
* {@link ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link TemporalPrecisionEnum#DAY}.
* <p>
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
* ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
@ -417,6 +440,7 @@ public class ModelConfig {
* <li>Coding.display</li>
* <li>Identifier.use.text</li>
* </ul>
*
* @since 5.0.0
*/
public boolean isSuppressStringIndexingInTokens() {
@ -432,12 +456,124 @@ public class ModelConfig {
* <li>Coding.display</li>
* <li>Identifier.use.text</li>
* </ul>
*
* @since 5.0.0
*/
public void setSuppressStringIndexingInTokens(boolean theSuppressStringIndexingInTokens) {
mySuppressStringIndexingInTokens = theSuppressStringIndexingInTokens;
}
/**
* When indexing a Period (e.g. Encounter.period) where the period has an upper bound
* but not a lower bound, a canned "start of time" value can be used as the lower bound
* in order to allow range searches to correctly identify all values in the range.
* <p>
* The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
* is probably good enough for almost any application, but this can be changed if
* needed.
* </p>
* <p>
* Note the following database documented limitations:
* <ul>
* <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
* <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
* <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
* <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
* <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
* </ul>
* </p>
*
* @see #getPeriodIndexEndOfTime()
* @since 5.1.0
*/
public IPrimitiveType<Date> getPeriodIndexStartOfTime() {
return myPeriodIndexStartOfTime;
}
/**
* When indexing a Period (e.g. Encounter.period) where the period has an upper bound
* but not a lower bound, a canned "start of time" value can be used as the lower bound
* in order to allow range searches to correctly identify all values in the range.
* <p>
* The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
* is probably good enough for almost any application, but this can be changed if
* needed.
* </p>
* <p>
* Note the following database documented limitations:
* <ul>
* <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
* <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
* <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
* <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
* <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
* </ul>
* </p>
*
* @see #getPeriodIndexEndOfTime()
* @since 5.1.0
*/
public void setPeriodIndexStartOfTime(IPrimitiveType<Date> thePeriodIndexStartOfTime) {
Validate.notNull(thePeriodIndexStartOfTime, "thePeriodIndexStartOfTime must not be null");
myPeriodIndexStartOfTime = thePeriodIndexStartOfTime;
}
/**
* When indexing a Period (e.g. Encounter.period) where the period has a lower bound
* but not an upper bound, a canned "end of time" value can be used as the upper bound
* in order to allow range searches to correctly identify all values in the range.
* <p>
* The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
* is probably good enough for almost any application, but this can be changed if
* needed.
* </p>
* <p>
* Note the following database documented limitations:
* <ul>
* <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
* <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
* <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
* <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
* <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
* </ul>
* </p>
*
* @see #getPeriodIndexStartOfTime()
* @since 5.1.0
*/
public IPrimitiveType<Date> getPeriodIndexEndOfTime() {
return myPeriodIndexEndOfTime;
}
/**
* When indexing a Period (e.g. Encounter.period) where the period has an upper bound
* but not a lower bound, a canned "start of time" value can be used as the lower bound
* in order to allow range searches to correctly identify all values in the range.
* <p>
* The default value for this is {@link #DEFAULT_PERIOD_INDEX_START_OF_TIME} which
* is probably good enough for almost any application, but this can be changed if
* needed.
* </p>
* <p>
* Note the following database documented limitations:
* <ul>
* <li>JDBC Timestamp Datatype Low Value -4713 and High Value 9999</li>
* <li>MySQL 8: the range for DATETIME values is '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999`</li>
* <li>Postgresql 12: Timestamp [without time zone] Low Value 4713 BC and High Value 294276 AD</li>
* <li>Oracle: Timestamp Low Value 4712 BC and High Value 9999 CE</li>
* <li>H2: datetime2 Low Value -4713 and High Value 9999</li>
* </ul>
* </p>
*
* @see #getPeriodIndexStartOfTime()
* @since 5.1.0
*/
public void setPeriodIndexEndOfTime(IPrimitiveType<Date> thePeriodIndexEndOfTime) {
Validate.notNull(thePeriodIndexEndOfTime, "thePeriodIndexEndOfTime must not be null");
myPeriodIndexEndOfTime = thePeriodIndexEndOfTime;
}
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.jpa.model.entity;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.IndexedEmbedded;
import javax.persistence.*;
import java.util.Set;
@Embeddable
public class ObservationIndexedCategoryCodeableConceptEntity {
@Field(name = "text")
private String myCodeableConceptText;
@IndexedEmbedded(depth=2, prefix = "coding")
@OneToMany(mappedBy = "myCodeableConceptId", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<ObservationIndexedCategoryCodingEntity> myObservationIndexedCategoryCodingEntitySet;
public ObservationIndexedCategoryCodeableConceptEntity(String theCodeableConceptText) {
setCodeableConceptText(theCodeableConceptText);
}
public void setObservationIndexedCategoryCodingEntitySet(Set<ObservationIndexedCategoryCodingEntity> theObservationIndexedCategoryCodingEntitySet) {
myObservationIndexedCategoryCodingEntitySet = theObservationIndexedCategoryCodingEntitySet;
}
public void setCodeableConceptText(String theCodeableConceptText) {
myCodeableConceptText = theCodeableConceptText;
}
}

View File

@ -0,0 +1,28 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import javax.persistence.*;
@Embeddable
public class ObservationIndexedCategoryCodingEntity {
@Field (name = "code", analyze = Analyze.NO)
private String myCode;
@Field (name = "system", analyze = Analyze.NO)
private String mySystem;
@Field (name = "code_system_hash", analyze = Analyze.NO)
private String myCodeSystemHash;
@Field (name = "display")
private String myDisplay;
public ObservationIndexedCategoryCodingEntity(String theSystem, String theCode, String theDisplay) {
myCode = theCode;
mySystem = theSystem;
myCodeSystemHash = String.valueOf(CodeSystemHash.hashCodeSystem(theSystem, theCode));
myDisplay = theDisplay;
}
}

View File

@ -0,0 +1,61 @@
package ca.uhn.fhir.jpa.model.entity;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import javax.persistence.*;
@Entity
@Indexed(index = "code_index")
@Embeddable
@Table(name = "HFJ_SPIDX_LASTN_CODE_CONCEPT")
public class ObservationIndexedCodeCodeableConceptEntity {
public static final int MAX_LENGTH = 200;
@Id
@DocumentId(name = "codeable_concept_id")
@Column(name="CODEABLE_CONCEPT_ID", length = MAX_LENGTH)
private String myCodeableConceptId;
@Field(name = "text")
@Column(name = "CODEABLE_CONCEPT_TEXT", nullable = true, length = MAX_LENGTH)
private String myCodeableConceptText;
@IndexedEmbedded(depth=2, prefix = "coding")
@JoinColumn(name = "CODEABLE_CONCEPT_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_CONCEPT_CODE"))
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private ObservationIndexedCodeCodingEntity myObservationIndexedCodeCodingEntity;
public ObservationIndexedCodeCodeableConceptEntity() {
}
public ObservationIndexedCodeCodeableConceptEntity(String theCodeableConceptText, String theCodeableConceptId) {
setCodeableConceptText(theCodeableConceptText);
setCodeableConceptId(theCodeableConceptId);
}
public void addCoding(ObservationIndexedCodeCodingEntity theObservationIndexedCodeCodingEntity) {
myObservationIndexedCodeCodingEntity = theObservationIndexedCodeCodingEntity;
}
public String getCodeableConceptId() {
return myCodeableConceptId;
}
public void setCodeableConceptId(String theCodeableConceptId) {
myCodeableConceptId = theCodeableConceptId;
}
public String getCodeableConceptText() {
return myCodeableConceptText;
}
public void setCodeableConceptText(String theCodeableConceptText) {
myCodeableConceptText = theCodeableConceptText;
}
}

View File

@ -0,0 +1,43 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import javax.persistence.*;
@Entity
@Embeddable
@Table(name = "HFJ_SPIDX_LASTN_CODING")
public class ObservationIndexedCodeCodingEntity {
public static final int MAX_LENGTH = 200;
@Id
@Column(name = "CODEABLE_CONCEPT_ID", length = MAX_LENGTH)
private String myCodeableConceptId;
@Field(name = "code", analyze = Analyze.NO)
private String myCode;
@Field(name = "system", analyze = Analyze.NO)
private String mySystem;
@Field(name = "code_system_hash", analyze = Analyze.NO)
private String myCodeSystemHash;
@Field(name = "display")
private String myDisplay;
public ObservationIndexedCodeCodingEntity() {
}
public ObservationIndexedCodeCodingEntity(String theSystem, String theCode, String theDisplay, String theCodeableConceptId) {
myCode = theCode;
mySystem = theSystem;
myCodeSystemHash = String.valueOf(CodeSystemHash.hashCodeSystem(theSystem, theCode));
myDisplay = theDisplay;
myCodeableConceptId = theCodeableConceptId;
}
}

View File

@ -0,0 +1,94 @@
package ca.uhn.fhir.jpa.model.entity;
import org.hibernate.search.annotations.*;
import javax.persistence.*;
import javax.persistence.Index;
import java.util.*;
@Entity
@Table(name = "HFJ_LASTN_OBSERVATION", indexes = {
@Index(name = "IDX_LASTN_OBSERVATION_RESID", columnList = "RESOURCE_IDENTIFIER", unique = true)
})
@Indexed(index = "observation_index")
public class ObservationIndexedSearchParamLastNEntity {
public static final int MAX_LENGTH = 200;
@Id
@SequenceGenerator(name = "SEQ_LASTN", sequenceName = "SEQ_LASTN")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_LASTN")
@Column(name = "LASTN_ID")
private Long myId;
@Field(name = "subject", analyze = Analyze.NO)
@Column(name = "LASTN_SUBJECT_ID", nullable = true, length = MAX_LENGTH)
private String mySubject;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CODEABLE_CONCEPT_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_OBSERVATION_CODE_FK"))
@IndexedEmbedded(depth = 2, prefix = "codeconcept")
private ObservationIndexedCodeCodeableConceptEntity myObservationCode;
@Field(name = "codeconceptid", analyze = Analyze.NO)
@Column(name = "CODEABLE_CONCEPT_ID", nullable = false, updatable = false, insertable = false, length = MAX_LENGTH)
private String myCodeNormalizedId;
@IndexedEmbedded(depth = 2, prefix = "categoryconcept")
@Transient
private Set<ObservationIndexedCategoryCodeableConceptEntity> myCategoryCodeableConcepts;
@Field(name = "effectivedtm", analyze = Analyze.NO)
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "LASTN_EFFECTIVE_DATETIME", nullable = true)
private Date myEffectiveDtm;
@DocumentId(name = "identifier")
@Column(name = "RESOURCE_IDENTIFIER", nullable = false, length = MAX_LENGTH)
private String myIdentifier;
public ObservationIndexedSearchParamLastNEntity() {
}
public String getSubject() {
return mySubject;
}
public void setSubject(String theSubject) {
mySubject = theSubject;
}
public String getIdentifier() {
return myIdentifier;
}
public void setIdentifier(String theIdentifier) {
myIdentifier = theIdentifier;
}
public void setEffectiveDtm(Date theEffectiveDtm) {
myEffectiveDtm = theEffectiveDtm;
}
public Date getEffectiveDtm() {
return myEffectiveDtm;
}
public void setCodeNormalizedId(String theCodeNormalizedId) {
myCodeNormalizedId = theCodeNormalizedId;
}
public String getCodeNormalizedId() {
return myCodeNormalizedId;
}
public void setObservationCode(ObservationIndexedCodeCodeableConceptEntity theObservationCode) {
myObservationCode = theObservationCode;
}
public void setCategoryCodeableConcepts(Set<ObservationIndexedCategoryCodeableConceptEntity> theCategoryCodeableConcepts) {
myCategoryCodeableConcepts = theCategoryCodeableConcepts;
}
}

View File

@ -59,7 +59,7 @@ import static org.apache.commons.lang3.StringUtils.trim;
@Index(name = "IDX_SP_TOKEN_HASH_S", columnList = "HASH_SYS"),
@Index(name = "IDX_SP_TOKEN_HASH_SV", columnList = "HASH_SYS_AND_VALUE"),
// TODO PERF change this to:
// @Index(name = "IDX_SP_TOKEN_HASH_V", columnList = "HASH_VALUE,RES_ID"),
// @Index(name = "IDX_SP_TOKEN_HASH_V", columnList = "HASH_VALUE,RES_ID"),
@Index(name = "IDX_SP_TOKEN_HASH_V", columnList = "HASH_VALUE"),
@Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"),
@ -232,6 +232,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
b.append(getHashValue());
b.append(getHashSystem());
b.append(getHashSystemAndValue());
return b.toHashCode();
}

View File

@ -37,6 +37,7 @@ public class SearchRuntimeDetails {
private boolean myLoadSynchronous;
private String myQueryString;
private SearchStatusEnum mySearchStatus;
private int myFoundIndexMatchesCount;
public SearchRuntimeDetails(RequestDetails theRequestDetails, String theSearchUuid) {
myRequestDetails = theRequestDetails;
mySearchUuid = theSearchUuid;
@ -67,6 +68,14 @@ public class SearchRuntimeDetails {
myFoundMatchesCount = theFoundMatchesCount;
}
public int getFoundIndexMatchesCount() {
return myFoundIndexMatchesCount;
}
public void setFoundIndexMatchesCount(int theFoundIndexMatchesCount) {
myFoundIndexMatchesCount = theFoundIndexMatchesCount;
}
public boolean getLoadSynchronous() {
return myLoadSynchronous;
}

View File

@ -0,0 +1,33 @@
package ca.uhn.fhir.jpa.model.util;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
public class CodeSystemHash {
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0);
private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8);
static public long hashCodeSystem( String system, String code ) {
Hasher hasher = HASH_FUNCTION.newHasher();
addStringToHasher(hasher, system);
addStringToHasher(hasher, code);
HashCode hashCode = hasher.hash();
return hashCode.asLong();
}
static private void addStringToHasher(Hasher hasher, String next) {
if (next == null) {
hasher.putByte((byte) 0);
} else {
next = UrlUtil.escapeUrlParam(next);
byte[] bytes = next.getBytes(Charsets.UTF_8);
hasher.putBytes(bytes);
}
hasher.putBytes(DELIMITER_BYTES);
}
}

View File

@ -161,6 +161,11 @@ public class JpaConstants {
* Operation name for the "$export-poll-status" operation
*/
public static final String OPERATION_EXPORT_POLL_STATUS = "$export-poll-status";
/**
* Operation name for the "$lastn" operation
*/
public static final String OPERATION_LASTN = "$lastn";
/**
* <p>
* This extension should be of type <code>string</code> and should be

View File

@ -61,6 +61,8 @@ public class SearchParameterMap implements Serializable {
private SummaryEnum mySummaryMode;
private SearchTotalModeEnum mySearchTotalMode;
private QuantityParam myNearDistanceParam;
private boolean myLastN;
private Integer myLastNMax;
/**
* Constructor
@ -303,6 +305,42 @@ public class SearchParameterMap implements Serializable {
return this;
}
/**
* If set, tells the server to use an Elasticsearch query to generate a list of
* Resource IDs for the LastN operation
*/
public boolean isLastN() {
return myLastN;
}
/**
* If set, tells the server to use an Elasticsearch query to generate a list of
* Resource IDs for the LastN operation
*/
public SearchParameterMap setLastN(boolean theLastN) {
myLastN = theLastN;
return this;
}
/**
* If set, tells the server the maximum number of observations to return for each
* observation code in the result set of a lastn operation
*/
public Integer getLastNMax() {
return myLastNMax;
}
/**
* If set, tells the server the maximum number of observations to return for each
* observation code in the result set of a lastn operation
*/
public SearchParameterMap setLastNMax(Integer theLastNMax) {
myLastNMax = theLastNMax;
return this;
}
/**
* This method creates a URL query string representation of the parameters in this
* object, excluding the part before the parameters, e.g.

View File

@ -162,17 +162,25 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
private IExtractor<PathAndRef> createReferenceExtractor() {
return (params, searchParam, value, path) -> {
if (value instanceof IBaseResource) {
return new ResourceLinkExtractor();
}
private class ResourceLinkExtractor implements IExtractor<PathAndRef> {
private PathAndRef myPathAndRef = null;
@Override
public void extract(SearchParamSet<PathAndRef> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
if (theValue instanceof IBaseResource) {
return;
}
String nextType = toRootTypeName(value);
String nextType = toRootTypeName(theValue);
switch (nextType) {
case "uri":
case "canonical":
String typeName = toTypeName(value);
IPrimitiveType<?> valuePrimitive = (IPrimitiveType<?>) value;
String typeName = toTypeName(theValue);
IPrimitiveType<?> valuePrimitive = (IPrimitiveType<?>) theValue;
IBaseReference fakeReference = (IBaseReference) myContext.getElementDefinition("Reference").newInstance();
fakeReference.setReference(valuePrimitive.getValueAsString());
@ -188,23 +196,23 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
*/
IIdType parsed = fakeReference.getReferenceElement();
if (parsed.hasIdPart() && parsed.hasResourceType() && !parsed.isAbsolute()) {
PathAndRef ref = new PathAndRef(searchParam.getName(), path, fakeReference, false);
params.add(ref);
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, false);
theParams.add(myPathAndRef);
break;
}
if (parsed.isAbsolute()) {
PathAndRef ref = new PathAndRef(searchParam.getName(), path, fakeReference, true);
params.add(ref);
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, true);
theParams.add(myPathAndRef);
break;
}
}
params.addWarning("Ignoring canonical reference (indexing canonical is not yet supported)");
theParams.addWarning("Ignoring canonical reference (indexing canonical is not yet supported)");
break;
case "reference":
case "Reference":
IBaseReference valueRef = (IBaseReference) value;
IBaseReference valueRef = (IBaseReference) theValue;
IIdType nextId = valueRef.getReferenceElement();
if (nextId.isEmpty() && valueRef.getResource() != null) {
@ -218,14 +226,27 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
return;
}
PathAndRef ref = new PathAndRef(searchParam.getName(), path, valueRef, false);
params.add(ref);
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, valueRef, false);
theParams.add(myPathAndRef);
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value);
addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue);
break;
}
};
}
public PathAndRef get(IBase theValue, String thePath) {
extract(new SearchParamSet<>(),
new RuntimeSearchParam(null, null, "Reference", null, null, null, null, null, null, null),
theValue, thePath);
return myPathAndRef;
}
}
@Override
public PathAndRef extractReferenceLinkFromResource(IBase theValue, String thePath) {
ResourceLinkExtractor extractor = new ResourceLinkExtractor();
return extractor.get(theValue, thePath);
}
@Override
@ -375,29 +396,126 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
private IExtractor<ResourceIndexedSearchParamDate> createDateExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
String nextType = toRootTypeName(value);
String resourceType = toRootTypeName(theResource);
return new DateExtractor(theResource);
}
private class DateExtractor implements IExtractor<ResourceIndexedSearchParamDate> {
String myResourceType;
ResourceIndexedSearchParamDate myIndexedSearchParamDate = null;
public DateExtractor(IBaseResource theResource) {
myResourceType = toRootTypeName(theResource);
}
public DateExtractor(String theResourceType) {
myResourceType = theResourceType;
}
@Override
public void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
String nextType = toRootTypeName(theValue);
switch (nextType) {
case "date":
case "dateTime":
case "instant":
addDateTimeTypes(resourceType, params, searchParam, value);
addDateTimeTypes(myResourceType, theParams, theSearchParam, theValue);
break;
case "Period":
addDate_Period(resourceType, params, searchParam, value);
addDate_Period(myResourceType, theParams, theSearchParam, theValue);
break;
case "Timing":
addDate_Timing(resourceType, params, searchParam, value);
addDate_Timing(myResourceType, theParams, theSearchParam, theValue);
break;
case "string":
// CarePlan.activitydate can be a string - ignored for now
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value);
addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue);
break;
}
};
}
private void addDate_Period(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Date start = extractValueAsDate(myPeriodStartValueChild, theValue);
String startAsString = extractValueAsString(myPeriodStartValueChild, theValue);
Date end = extractValueAsDate(myPeriodEndValueChild, theValue);
String endAsString = extractValueAsString(myPeriodEndValueChild, theValue);
if (start != null || end != null) {
if (start == null) {
start = myModelConfig.getPeriodIndexStartOfTime().getValue();
startAsString = myModelConfig.getPeriodIndexStartOfTime().getValueAsString();
}
if (end == null) {
end = myModelConfig.getPeriodIndexEndOfTime().getValue();
endAsString = myModelConfig.getPeriodIndexEndOfTime().getValueAsString();
}
myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString);
theParams.add(myIndexedSearchParamDate);
}
}
private void addDate_Timing(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue);
TreeSet<Date> dates = new TreeSet<>();
String firstValue = null;
String finalValue = null;
for (IPrimitiveType<Date> nextEvent : values) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
finalValue = nextEvent.getValueAsString();
}
}
Optional<IBase> repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue);
if (repeat.isPresent()) {
Optional<IBase> bounds = myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get());
if (bounds.isPresent()) {
String boundsType = toRootTypeName(bounds.get());
if ("Period".equals(boundsType)) {
Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get());
Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get());
dates.add(start);
dates.add(end);
}
}
}
if (!dates.isEmpty()) {
myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), dates.first(), firstValue, dates.last(), finalValue, firstValue);
theParams.add(myIndexedSearchParamDate);
}
}
@SuppressWarnings("unchecked")
private void addDateTimeTypes(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<Date> nextBaseDateTime = (IPrimitiveType<Date>) theValue;
if (nextBaseDateTime.getValue() != null) {
myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString());
theParams.add(myIndexedSearchParamDate);
}
}
public ResourceIndexedSearchParamDate get(IBase theValue, String thePath) {
extract(new SearchParamSet<>(),
new RuntimeSearchParam(null, null, "date", null, null, null, null, null, null, null),
theValue, thePath);
return myIndexedSearchParamDate;
}
}
@Override
public Date extractDateFromResource(IBase theValue, String thePath) {
DateExtractor extractor = new DateExtractor("DateType");
return extractor.get(theValue, thePath).getValueHigh();
}
@Override
@ -508,7 +626,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
/**
* Override parent because we're using FHIRPath here
*/
private List<IBase> extractValues(String thePaths, IBaseResource theResource) {
@Override
public List<IBase> extractValues(String thePaths, IBaseResource theResource) {
List<IBase> values = new ArrayList<>();
if (isNotBlank(thePaths)) {
String[] nextPathsSplit = split(thePaths);
@ -626,30 +745,73 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
private void addToken_CodeableConcept(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
List<IBase> codings = myCodeableConceptCodingValueChild.getAccessor().getValues(theValue);
List<IBase> codings = getCodingsFromCodeableConcept(theValue);
for (IBase nextCoding : codings) {
addToken_Coding(theResourceType, theParams, theSearchParam, nextCoding);
}
if (shouldIndexTextComponentOfToken(theSearchParam)) {
String text = extractValueAsString(myCodeableConceptTextValueChild, theValue);
String text = getDisplayTextFromCodeableConcept(theValue);
if (isNotBlank(text)) {
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
}
}
}
@Override
public List<IBase> getCodingsFromCodeableConcept(IBase theValue) {
String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
if ("CodeableConcept".equals(nextType)) {
return myCodeableConceptCodingValueChild.getAccessor().getValues(theValue);
} else {
return null;
}
}
@Override
public String getDisplayTextFromCodeableConcept(IBase theValue) {
String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
if ("CodeableConcept".equals(nextType)) {
return extractValueAsString(myCodeableConceptTextValueChild, theValue);
} else {
return null;
}
}
private void addToken_Coding(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
String system = extractValueAsString(myCodingSystemValueChild, theValue);
String code = extractValueAsString(myCodingCodeValueChild, theValue);
createTokenIndexIfNotBlank(theResourceType, theParams, theSearchParam, system, code);
ResourceIndexedSearchParamToken resourceIndexedSearchParamToken = createSearchParamForCoding(theResourceType, theSearchParam, theValue);
if (resourceIndexedSearchParamToken != null) {
theParams.add(resourceIndexedSearchParamToken);
}
if (shouldIndexTextComponentOfToken(theSearchParam)) {
String text = extractValueAsString(myCodingDisplayValueChild, theValue);
String text = getDisplayTextForCoding(theValue);
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
}
}
@Override
public ResourceIndexedSearchParamToken createSearchParamForCoding(String theResourceType, RuntimeSearchParam theSearchParam, IBase theValue) {
String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
if ("Coding".equals(nextType)) {
String system = extractValueAsString(myCodingSystemValueChild, theValue);
String code = extractValueAsString(myCodingCodeValueChild, theValue);
return createTokenIndexIfNotBlank(theResourceType, theSearchParam, system, code);
} else {
return null;
}
}
@Override
public String getDisplayTextForCoding(IBase theValue) {
String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
if ("Coding".equals(nextType)) {
return extractValueAsString(myCodingDisplayValueChild, theValue);
} else {
return null;
}
}
private void addToken_ContactPoint(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
String system = extractValueAsString(myContactPointSystemValueChild, theValue);
String value = extractValueAsString(myContactPointValueValueChild, theValue);
@ -677,6 +839,16 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String endAsString = extractValueAsString(myPeriodEndValueChild, theValue);
if (start != null || end != null) {
if (start == null) {
start = myModelConfig.getPeriodIndexStartOfTime().getValue();
startAsString = myModelConfig.getPeriodIndexStartOfTime().getValueAsString();
}
if (end == null) {
end = myModelConfig.getPeriodIndexEndOfTime().getValue();
endAsString = myModelConfig.getPeriodIndexEndOfTime().getValueAsString();
}
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString);
theParams.add(nextEntity);
}
@ -895,26 +1067,19 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
}
private String toRootTypeName(IBase nextObject) {
@Override
public String toRootTypeName(IBase nextObject) {
BaseRuntimeElementDefinition<?> elementDefinition = getContext().getElementDefinition(nextObject.getClass());
BaseRuntimeElementDefinition<?> rootParentDefinition = elementDefinition.getRootParentDefinition();
return rootParentDefinition.getName();
}
private String toTypeName(IBase nextObject) {
@Override
public String toTypeName(IBase nextObject) {
BaseRuntimeElementDefinition<?> elementDefinition = getContext().getElementDefinition(nextObject.getClass());
return elementDefinition.getName();
}
@SuppressWarnings("unchecked")
private void addDateTimeTypes(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<Date> nextBaseDateTime = (IPrimitiveType<Date>) theValue;
if (nextBaseDateTime.getValue() != null) {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString());
theParams.add(param);
}
}
private void addUri_Uri(String theResourceType, Set<ResourceIndexedSearchParamUri> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<?> value = (IPrimitiveType<?>) theValue;
String valueAsString = value.getValueAsString();
@ -946,8 +1111,16 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
private void createTokenIndexIfNotBlank(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theSystem, String theValue) {
ResourceIndexedSearchParamToken nextEntity = createTokenIndexIfNotBlank(theResourceType, theSearchParam, theSystem, theValue);
if (nextEntity != null) {
theParams.add(nextEntity);
}
}
private ResourceIndexedSearchParamToken createTokenIndexIfNotBlank(String theResourceType, RuntimeSearchParam theSearchParam, String theSystem, String theValue) {
String system = theSystem;
String value = theValue;
ResourceIndexedSearchParamToken nextEntity = null;
if (isNotBlank(system) || isNotBlank(value)) {
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
@ -956,10 +1129,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
value = value.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
ResourceIndexedSearchParamToken nextEntity;
nextEntity = new ResourceIndexedSearchParamToken(myPartitionSettings, theResourceType, theSearchParam.getName(), system, value);
theParams.add(nextEntity);
}
return nextEntity;
}
@Override

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
@ -11,6 +13,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
@ -60,6 +63,23 @@ public interface ISearchParamExtractor {
List<String> extractParamValuesAsStrings(RuntimeSearchParam theActiveSearchParam, IBaseResource theResource);
List<IBase> extractValues(String thePaths, IBaseResource theResource);
String toRootTypeName(IBase nextObject);
String toTypeName(IBase nextObject);
PathAndRef extractReferenceLinkFromResource(IBase theValue, String thePath);
Date extractDateFromResource(IBase theValue, String thePath);
ResourceIndexedSearchParamToken createSearchParamForCoding(String theResourceType, RuntimeSearchParam theSearchParam, IBase theValue);
String getDisplayTextForCoding(IBase theValue);
List<IBase> getCodingsFromCodeableConcept(IBase theValue);
String getDisplayTextFromCodeableConcept(IBase theValue);
class SearchParamSet<T> extends HashSet<T> {

View File

@ -0,0 +1,115 @@
package ca.uhn.fhir.jpa.searchparam.util;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class LastNParameterHelper {
public static boolean isLastNParameter(String theParamName, FhirContext theContext) {
if (theParamName == null) {
return false;
}
FhirVersionEnum version = theContext.getVersion().getVersion();
if (isR5(version) && isLastNParameterR5(theParamName)) {
return true;
} else if (isR4(version) && isLastNParameterR4(theParamName)) {
return true;
} else if (isDstu3(version) && isLastNParameterDstu3(theParamName)) {
return true;
} else {
return false;
}
}
private static boolean isDstu3(FhirVersionEnum theVersion) {
return (theVersion == FhirVersionEnum.DSTU3);
}
private static boolean isR4(FhirVersionEnum theVersion) {
return (theVersion == FhirVersionEnum.R4);
}
private static boolean isR5(FhirVersionEnum theVersion) {
return (theVersion == FhirVersionEnum.R5);
}
private static boolean isLastNParameterDstu3(String theParamName) {
return (theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_PATIENT)
|| theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CODE));
}
private static boolean isLastNParameterR4(String theParamName) {
return (theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_PATIENT)
|| theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_CODE));
}
private static boolean isLastNParameterR5(String theParamName) {
return (theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_PATIENT)
|| theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CODE));
}
public static String getSubjectParamName(FhirContext theContext) {
if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) {
return org.hl7.fhir.r5.model.Observation.SP_SUBJECT;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.R4) {
return org.hl7.fhir.r4.model.Observation.SP_SUBJECT;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
return org.hl7.fhir.dstu3.model.Observation.SP_SUBJECT;
} else {
throw new InvalidRequestException("$lastn operation is not implemented for FHIR Version " + theContext.getVersion().getVersion().getFhirVersionString());
}
}
public static String getPatientParamName(FhirContext theContext) {
if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) {
return org.hl7.fhir.r5.model.Observation.SP_PATIENT;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.R4) {
return org.hl7.fhir.r4.model.Observation.SP_PATIENT;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
return org.hl7.fhir.dstu3.model.Observation.SP_PATIENT;
} else {
throw new InvalidRequestException("$lastn operation is not implemented for FHIR Version " + theContext.getVersion().getVersion().getFhirVersionString());
}
}
public static String getEffectiveParamName(FhirContext theContext) {
if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) {
return org.hl7.fhir.r5.model.Observation.SP_DATE;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.R4) {
return org.hl7.fhir.r4.model.Observation.SP_DATE;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
return org.hl7.fhir.dstu3.model.Observation.SP_DATE;
} else {
throw new InvalidRequestException("$lastn operation is not implemented for FHIR Version " + theContext.getVersion().getVersion().getFhirVersionString());
}
}
public static String getCategoryParamName(FhirContext theContext) {
if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) {
return org.hl7.fhir.r5.model.Observation.SP_CATEGORY;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.R4) {
return org.hl7.fhir.r4.model.Observation.SP_CATEGORY;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
return org.hl7.fhir.dstu3.model.Observation.SP_CATEGORY;
} else {
throw new InvalidRequestException("$lastn operation is not implemented for FHIR Version " + theContext.getVersion().getVersion().getFhirVersionString());
}
}
public static String getCodeParamName(FhirContext theContext) {
if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) {
return org.hl7.fhir.r5.model.Observation.SP_CODE;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.R4) {
return org.hl7.fhir.r4.model.Observation.SP_CODE;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
return org.hl7.fhir.dstu3.model.Observation.SP_CODE;
} else {
throw new InvalidRequestException("$lastn operation is not implemented for FHIR Version " + theContext.getVersion().getVersion().getFhirVersionString());
}
}
}

View File

@ -41,6 +41,7 @@ import javax.mail.internet.MimeMessage;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
@ -53,6 +54,7 @@ public class JavaMailEmailSender implements IEmailSender {
private JavaMailSenderImpl mySender;
private String mySmtpServerUsername;
private String mySmtpServerPassword;
private final Properties myJavaMailProperties = new Properties();
public String getSmtpServerHostname() {
return mySmtpServerHostname;
@ -92,6 +94,38 @@ public class JavaMailEmailSender implements IEmailSender {
mySmtpServerUsername = theSmtpServerUsername;
}
/**
* Set the "mail.smtp.auth" Java Mail Property
*/
public void setAuth(Boolean theAuth) {
myJavaMailProperties.setProperty("mail.smtp.auth", theAuth.toString());
}
/**
* Set the "mail.smtp.starttls.enable" Java Mail Property
*/
public void setStartTlsEnable(Boolean theStartTlsEnable) {
myJavaMailProperties.setProperty("mail.smtp.starttls.enable", theStartTlsEnable.toString());
}
/**
* Set the "mail.smtp.starttls.required" Java Mail Property
*/
public void setStartTlsRequired(Boolean theStartTlsRequired) {
myJavaMailProperties.setProperty("mail.smtp.starttls.required", theStartTlsRequired.toString());
}
/**
* Set the "mail.smtp.quitwait" Java Mail Property
*/
public void setQuitWait(Boolean theQuitWait) {
myJavaMailProperties.setProperty("mail.smtp.quitwait", theQuitWait.toString());
}
@Override
public void send(EmailDetails theDetails) {
String subscriptionId = theDetails.getSubscription().toUnqualifiedVersionless().getValue();
@ -144,6 +178,7 @@ public class JavaMailEmailSender implements IEmailSender {
mySender.setUsername(getSmtpServerUsername());
mySender.setPassword(getSmtpServerPassword());
mySender.setDefaultEncoding(Constants.CHARSET_UTF8.name());
mySender.setJavaMailProperties(myJavaMailProperties);
}
private static String toTrimmedCommaSeparatedString(List<String> theTo) {

View File

@ -24,7 +24,7 @@ import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
public class ${className}ResourceProvider extends
## We have specialized base classes for RPs that handle certain resource types. These
## RPs implement type specific operations
#if ( $version != 'dstu' && (${className} == 'Encounter' || ${className} == 'Patient' || ${className} == 'ValueSet' || ${className} == 'QuestionnaireAnswers' || ${className} == 'CodeSystem' || ($version != 'dstu2' && ${className} == 'ConceptMap') || ${className} == 'MessageHeader' || ${className} == 'Composition' || ${className} == 'StructureDefinition'))
#if ( $version != 'dstu' && (${className} == 'Encounter' || ${className} == 'Patient' || ${className} == 'ValueSet' || ${className} == 'QuestionnaireAnswers' || ${className} == 'CodeSystem' || ($version != 'dstu2' && ${className} == 'ConceptMap') || ${className} == 'MessageHeader' || ${className} == 'Composition' || ${className} == 'StructureDefinition' || ($version != 'dstu2' && ${className} == 'Observation') ))
BaseJpaResourceProvider${className}${versionCapitalized}
#else
JpaResourceProvider${versionCapitalized}<${className}>

View File

@ -50,6 +50,8 @@
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${res.name}${versionCapitalized}">
#elseif ( ${versionCapitalized} != 'Dstu1' && ( ${res.name} == 'Bundle' || ${res.name} == 'Encounter' || ${res.name} == 'Composition' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'ValueSet' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'SearchParameter'))
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${res.name}${versionCapitalized}">
#elseif ( ${versionCapitalized} != 'Dstu1' && ${versionCapitalized} != 'Dstu2' && ${res.name} == 'Observation')
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${res.name}${versionCapitalized}">
#else
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}">
#end

View File

@ -69,6 +69,8 @@ public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jp
IFhirResourceDaoConceptMap<org.hl7.fhir.${version}.model.ConceptMap>
#elseif ( ${versionCapitalized} != 'Dstu1' && (${res.name} == 'Composition' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'SearchParameter' || ${res.name} == 'MessageHeader' || ${res.name} == 'StructureDefinition'))
IFhirResourceDao${res.name}<${resourcePackage}.${res.declaringClassNameComplete}>
#elseif ( ${versionCapitalized} != 'Dstu1' && ${versionCapitalized} != 'Dstu2' && (${res.name} == 'Observation'))
IFhirResourceDao${res.name}<${resourcePackage}.${res.declaringClassNameComplete}>
#else
IFhirResourceDao<${resourcePackage}.${res.declaringClassNameComplete}>
#end
@ -83,6 +85,9 @@ public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jp
#elseif ( ${res.name} == 'Bundle' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'ValueSet' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'SearchParameter' || ${res.name} == 'CodeSystem' || ${res.name} == 'MessageHeader' || ${res.name} == 'Composition' || ${res.name} == 'StructureDefinition')
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal;
retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}();
#elseif ( ${versionCapitalized} != 'Dstu1' && ${versionCapitalized} != 'Dstu2' && ${res.name} == 'Observation')
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal;
retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}();
#else
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao<${resourcePackage}.${res.declaringClassNameComplete}> retVal;
retVal = new ca.uhn.fhir.jpa.dao.JpaResourceDao<${resourcePackage}.${res.declaringClassNameComplete}>();

23
pom.xml
View File

@ -1644,7 +1644,7 @@
<plugin>
<groupId>org.basepom.maven</groupId>
<artifactId>duplicate-finder-maven-plugin</artifactId>
<version>1.3.0</version>
<version>1.4.0</version>
</plugin>
<plugin>
<groupId>de.jpdigital</groupId>
@ -2043,26 +2043,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-property</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>11</version>
</requireJavaVersion>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<inherited>false</inherited>
@ -2578,6 +2558,7 @@
<module>hapi-fhir-structures-r5</module>
<module>hapi-fhir-validation-resources-r5</module>
<module>hapi-fhir-igpacks</module>
<module>hapi-fhir-elasticsearch-6</module>
<module>hapi-fhir-jpaserver-api</module>
<module>hapi-fhir-jpaserver-model</module>
<module>hapi-fhir-jpaserver-searchparam</module>