1
0
mirror of https://github.com/hapifhir/hapi-fhir.git synced 2025-03-28 02:48:47 +00:00

Initial implementation of lastn operation.

This commit is contained in:
ianmarshall 2020-03-31 21:55:57 -04:00
parent f891634341
commit f819c91530
46 changed files with 2144 additions and 181 deletions
hapi-fhir-cli
hapi-fhir-jpaserver-base/src
hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util
hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam
hapi-tinder-plugin/src/main/resources/vm

@ -173,6 +173,7 @@ public abstract class BaseApp {
commands.add(new ExportConceptMapToCsvCommand());
commands.add(new ImportCsvToConceptMapCommand());
commands.add(new HapiFlywayMigrateDatabaseCommand());
commands.add(new RunJpaServerWithElasticsearchCommand());
return commands;
}

@ -0,0 +1,250 @@
package ca.uhn.fhir.cli;
/*-
* #%L
* HAPI FHIR - Command Line Client - API
* %%
* 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.DaoConfig;
import ca.uhn.fhir.jpa.demo.*;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.*;
import java.net.SocketException;
public class RunJpaServerWithElasticsearchCommand extends BaseCommand {
private static final String OPTION_DISABLE_REFERENTIAL_INTEGRITY = "disable-referential-integrity";
private static final String OPTION_LOWMEM = "lowmem";
private static final String OPTION_ALLOW_EXTERNAL_REFS = "allow-external-refs";
private static final String OPTION_REUSE_SEARCH_RESULTS_MILLIS = "reuse-search-results-milliseconds";
private static final String OPTION_EXTERNAL_ELASTICSEARCH = "external-elasticsearch";
private static final int DEFAULT_PORT = 8080;
private static final String OPTION_P = "p";
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RunJpaServerWithElasticsearchCommand.class);
public static final String RUN_SERVER_COMMAND_ELASTICSEARCH = "run-server-elasticsearch";
private int myPort;
private Server myServer;
@Override
public String getCommandName() {
return RUN_SERVER_COMMAND_ELASTICSEARCH;
}
@Override
public Options getOptions() {
Options options = new Options();
addFhirVersionOption(options);
options.addOption(OPTION_P, "port", true, "The port to listen on (default is " + DEFAULT_PORT + ")");
options.addOption(null, OPTION_LOWMEM, false, "If this flag is set, the server will operate in low memory mode (some features disabled)");
options.addOption(null, OPTION_ALLOW_EXTERNAL_REFS, false, "If this flag is set, the server will allow resources to be persisted contaning external resource references");
options.addOption(null, OPTION_DISABLE_REFERENTIAL_INTEGRITY, false, "If this flag is set, the server will not enforce referential integrity");
options.addOption(null, OPTION_EXTERNAL_ELASTICSEARCH, false, "If this flag is set, the server will attempt to use an external elasticsearch instance listening on port 9301");
addOptionalOption(options, "u", "url", "Url", "If this option is set, specifies the JDBC URL to use for the database connection");
Long defaultReuseSearchResults = DaoConfig.DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS;
String defaultReuseSearchResultsStr = defaultReuseSearchResults == null ? "off" : String.valueOf(defaultReuseSearchResults);
options.addOption(null, OPTION_REUSE_SEARCH_RESULTS_MILLIS, true, "The time in milliseconds within which the same results will be returned for multiple identical searches, or \"off\" (default is " + defaultReuseSearchResultsStr + ")");
return options;
}
private int parseOptionInteger(CommandLine theCommandLine, String opt, int defaultPort) throws ParseException {
try {
return Integer.parseInt(theCommandLine.getOptionValue(opt, Integer.toString(defaultPort)));
} catch (NumberFormatException e) {
throw new ParseException("Invalid value '" + theCommandLine.getOptionValue(opt) + "' (must be numeric)");
}
}
@Override
public void run(CommandLine theCommandLine) throws ParseException {
parseFhirContext(theCommandLine);
myPort = parseOptionInteger(theCommandLine, OPTION_P, DEFAULT_PORT);
if (theCommandLine.hasOption(OPTION_LOWMEM)) {
ourLog.info("Running in low memory mode, some features disabled");
System.setProperty(OPTION_LOWMEM, OPTION_LOWMEM);
}
if (theCommandLine.hasOption(OPTION_ALLOW_EXTERNAL_REFS)) {
ourLog.info("Server is configured to allow external references");
ContextPostgreSQLHolder.setAllowExternalRefs(true);
}
if (theCommandLine.hasOption(OPTION_DISABLE_REFERENTIAL_INTEGRITY)) {
ourLog.info("Server is configured to not enforce referential integrity");
ContextPostgreSQLHolder.setDisableReferentialIntegrity(true);
}
if (theCommandLine.hasOption(OPTION_EXTERNAL_ELASTICSEARCH)) {
ourLog.info("Server is configured to use external elasticsearch");
ContextPostgreSQLHolder.setExternalElasticsearch(true);
}
ContextPostgreSQLHolder.setDatabaseUrl(theCommandLine.getOptionValue("u"));
String reuseSearchResults = theCommandLine.getOptionValue(OPTION_REUSE_SEARCH_RESULTS_MILLIS);
if (reuseSearchResults != null) {
if (reuseSearchResults.equals("off")) {
ourLog.info("Server is configured to not reuse search results");
ContextPostgreSQLHolder.setReuseCachedSearchResultsForMillis(null);
} else {
try {
long reuseSearchResultsMillis = Long.parseLong(reuseSearchResults);
if (reuseSearchResultsMillis < 0) {
throw new NumberFormatException("expected a positive integer");
}
ourLog.info("Server is configured to reuse search results for " + String.valueOf(reuseSearchResultsMillis) + " milliseconds");
ContextPostgreSQLHolder.setReuseCachedSearchResultsForMillis(reuseSearchResultsMillis);
} catch (NumberFormatException e) {
throw new ParseException("Invalid value '" + reuseSearchResults + "' (must be a positive integer)");
}
}
}
ContextPostgreSQLHolder.setCtx(getFhirContext());
ourLog.info("Preparing HAPI FHIR JPA server on port {}", myPort);
File tempWarFile;
try {
tempWarFile = File.createTempFile("hapi-fhir", ".war");
tempWarFile.deleteOnExit();
InputStream inStream = RunJpaServerWithElasticsearchCommand.class.getResourceAsStream("/hapi-fhir-cli-jpaserver.war");
OutputStream outStream = new BufferedOutputStream(new FileOutputStream(tempWarFile, false));
IOUtils.copy(inStream, outStream);
} catch (IOException e) {
ourLog.error("Failed to create temporary file", e);
return;
}
final ContextLoaderListener cll = new ContextLoaderListener();
ourLog.info("Starting HAPI FHIR JPA server in {} mode", ContextPostgreSQLHolder.getCtx().getVersion().getVersion());
WebAppContext root = new WebAppContext();
root.setAllowDuplicateFragmentNames(true);
root.setWar(tempWarFile.getAbsolutePath());
root.setParentLoaderPriority(true);
root.setContextPath("/");
root.addEventListener(new ServletContextListener() {
@Override
public void contextInitialized(ServletContextEvent theSce) {
theSce.getServletContext().setInitParameter(ContextLoader.CONTEXT_CLASS_PARAM, AnnotationConfigWebApplicationContext.class.getName());
switch (ContextPostgreSQLHolder.getCtx().getVersion().getVersion()) {
case DSTU2:
theSce.getServletContext().setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, FhirServerConfig.class.getName());
break;
case DSTU3:
theSce.getServletContext().setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, FhirServerConfigDstu3.class.getName());
break;
case R4:
theSce.getServletContext().setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, FhirServerElasticsearchConfigR4.class.getName());
break;
case DSTU2_1:
case DSTU2_HL7ORG:
break;
}
cll.contextInitialized(theSce);
}
@Override
public void contextDestroyed(ServletContextEvent theSce) {
cll.contextDestroyed(theSce);
}
});
String path = ContextPostgreSQLHolder.getPath();
root.addServlet("ca.uhn.fhir.jpa.demo.JpaServerDemo", path + "*");
myServer = new Server(myPort);
myServer.setHandler(root);
try {
myServer.start();
} catch (SocketException e) {
throw new CommandFailureException("Server failed to start on port " + myPort + " because of the following error \"" + e.toString() + "\". Note that you can use the '-p' option to specify an alternate port.");
} catch (Exception e) {
ourLog.error("Server failed to start", e);
throw new CommandFailureException("Server failed to start", e);
}
ourLog.info("Server started on port {}", myPort);
ourLog.info("Web Testing UI : http://localhost:{}/", myPort);
ourLog.info("Server Base URL: http://localhost:{}{}", myPort, path);
// Never quit.. We'll let the user ctrl-C their way out.
loopForever();
}
@SuppressWarnings("InfiniteLoopStatement")
private void loopForever() {
while (true) {
try {
Thread.sleep(DateUtils.MILLIS_PER_MINUTE);
} catch (InterruptedException theE) {
// ignore
}
}
}
public static void main(String[] theArgs) {
Server server = new Server(22);
String path = "../hapi-fhir-cli-jpaserver";
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml");
webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-example");
webAppContext.setParentLoaderPriority(true);
server.setHandler(webAppContext);
try {
server.start();
} catch (Exception e) {
e.printStackTrace();
}
ourLog.info("Started");
}
@Override
public String getCommandDescription() {
return "Start a FHIR server which can be used for testing";
}
}

@ -136,6 +136,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
</dependencies>
<build>

@ -35,7 +35,8 @@ import java.util.Properties;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@SuppressWarnings("Duplicates")
@Configuration
// TODO: Merge this with new CommonPostgreSQLConfig or find way to avoid conflicts with it.
//@Configuration
public class CommonConfig {
/**

@ -0,0 +1,180 @@
package ca.uhn.fhir.jpa.demo;
/*-
* #%L
* HAPI FHIR - Command Line Client - Server WAR
* %%
* 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.context.ConfigurationException;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
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;
@SuppressWarnings("Duplicates")
@Configuration
public class CommonPostgreSQLConfig {
static String elasticsearchHost = "localhost";
static String elasticsearchUserId = "";
static String elasticsearchPassword = "";
static Integer elasticsearchPort;
/**
* Configure FHIR properties around the the JPA server via this bean
*/
@Bean
public DaoConfig daoConfig() {
DaoConfig retVal = new DaoConfig();
retVal.setSubscriptionEnabled(true);
retVal.setSubscriptionPollDelay(5000);
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
retVal.setAllowMultipleDelete(true);
return retVal;
}
@Bean
public ModelConfig modelConfig() {
return daoConfig().getModelConfig();
}
/**
* The following bean configures the database connection. The 'url' property value of "jdbc:postgresql://localhost:5432/hapi" indicates that the server should save resources in a
* PostgreSQL database named "hapi".
*
* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource.
*/
@Bean(destroyMethod = "close")
public DataSource dataSource() {
String dbUrl = "jdbc:postgresql://localhost:5432/hapi";
String dbUsername = "hapi";
String dbPassword = "HapiFHIR";
if (isNotBlank(ContextPostgreSQLHolder.getDatabaseUrl())) {
dbUrl = ContextPostgreSQLHolder.getDatabaseUrl();
}
BasicDataSource retVal = new BasicDataSource();
retVal.setDriverClassName("org.postgresql.Driver");
retVal.setUrl(dbUrl);
retVal.setUsername(dbUsername);
retVal.setPassword(dbPassword);
return retVal;
}
@Bean
public Properties jpaProperties() {
if(ContextPostgreSQLHolder.isExternalElasticsearch()) {
elasticsearchUserId = "elastic";
elasticsearchPassword = "changeme";
elasticsearchPort = 9301;
} else {
elasticsearchPort = embeddedElasticSearch().getHttpPort();
}
Properties extraProperties = new Properties();
extraProperties.put("hibernate.dialect", org.hibernate.dialect.PostgreSQL94Dialect.class.getName());
extraProperties.put("hibernate.format_sql", "false");
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.jdbc.batch_size", "20");
extraProperties.put("hibernate.cache.use_query_cache", "false");
extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "local-heap");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.default.worker.execution", "sync");
if (System.getProperty("lowmem") != null) {
extraProperties.put("hibernate.search.autoregister_listeners", "false");
}
new ElasticsearchHibernatePropertiesBuilder()
.setDebugRefreshAfterWrite(true)
.setDebugPrettyPrintJsonLog(true)
.setIndexSchemaManagementStrategy(IndexSchemaManagementStrategy.CREATE)
.setIndexManagementWaitTimeoutMillis(10000)
.setRequiredIndexStatus(ElasticsearchIndexStatus.YELLOW)
.setRestUrl("http://" + elasticsearchHost + ":" + elasticsearchPort)
.setUsername(elasticsearchUserId)
.setPassword(elasticsearchPassword)
.apply(extraProperties);
// extraProperties.setProperty("hibernate.search.default.elasticsearch.refresh_after_write", "true");
return extraProperties;
}
@Bean()
public ElasticsearchSvcImpl myElasticsearchSvc() throws IOException {
if(ContextPostgreSQLHolder.isExternalElasticsearch()) {
elasticsearchUserId = "elastic";
elasticsearchPassword = "changeme";
elasticsearchPort = 9301;
} else {
elasticsearchPort = embeddedElasticSearch().getHttpPort();
}
return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUserId, elasticsearchPassword);
}
@Bean
public EmbeddedElastic embeddedElasticSearch() {
String ELASTIC_VERSION = "6.5.4";
EmbeddedElastic embeddedElastic;
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();
}
}

@ -0,0 +1,89 @@
package ca.uhn.fhir.jpa.demo;
/*-
* #%L
* HAPI FHIR - Command Line Client - Server WAR
* %%
* 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 ContextPostgreSQLHolder extends ContextHolder {
// private static String myDbUsername;
// private static String myDbPassword;
private static boolean myExternalElasticsearch = false;
// private static String myElasticsearchHost;
// private static Integer myElasticsearchPort;
// private static String myElasticsearchUsername;
// private static String myElasticsearchPassword;
/* public static void setDbUsername(String theDbUsername) {
myDbUsername = theDbUsername;
}
public static String getDbUsername() {
return myDbUsername;
}
public static void setDbPassword(String theDbPassword) {
myDbPassword = theDbPassword;
}
public static String getDbPassword() {
return myDbPassword;
}
*/
public static void setExternalElasticsearch(Boolean theExternalElasticsearch) {
myExternalElasticsearch = theExternalElasticsearch;
}
public static Boolean isExternalElasticsearch() {
return myExternalElasticsearch;
}
/*
public static void setElasticsearchHost(String theElasticsearchHost) {
myElasticsearchHost = theElasticsearchHost;
}
public static String getElasticsearchHost() {
return myElasticsearchHost;
}
public static void setElasticsearchPort(Integer theElasticsearchPort) {
myElasticsearchPort = theElasticsearchPort;
}
public static Integer getElasticsearchPort() {
return myElasticsearchPort;
}
public static void setElasticsearchUsername(String theElasticsearchUsername) {
myElasticsearchUsername = theElasticsearchUsername;
}
public static String getElasticsearchUsername() {
return myElasticsearchUsername;
}
public static void setElasticsearchPassword(String theElasticsearchPassword) {
myElasticsearchPassword = theElasticsearchPassword;
}
public static String getElasticsearchPassword() {
return myElasticsearchPassword;
}
*/
}

@ -0,0 +1,107 @@
package ca.uhn.fhir.jpa.demo;
/*-
* #%L
* HAPI FHIR - Command Line Client - Server WAR
* %%
* 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.config.BaseJavaConfigR4;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorR4;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
/**
* This class isn't used by default by the example, but
* you can use it as a config if you want to support DSTU3
* instead of DSTU2 in your server.
* <p>
* See https://github.com/jamesagnew/hapi-fhir/issues/278
*/
@Configuration
@EnableTransactionManagement()
@Import(CommonPostgreSQLConfig.class)
public class FhirServerElasticsearchConfigR4 extends BaseJavaConfigR4 {
@Autowired
private DataSource myDataSource;
@Autowired()
@Qualifier("jpaProperties")
private Properties myJpaProperties;
@Override
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
retVal.setDataSource(myDataSource);
retVal.setJpaProperties(myJpaProperties);
return retVal;
}
/**
* Do some fancy logging to create a nice access log that has details about each incoming request.
* @return
*/
public LoggingInterceptor loggingInterceptor() {
LoggingInterceptor retVal = new LoggingInterceptor();
retVal.setLoggerName("fhirtest.access");
retVal.setMessageFormat(
"Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]");
retVal.setLogExceptions(true);
retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}");
return retVal;
}
/**
* This interceptor adds some pretty syntax highlighting in responses when a browser is detected
* @return
*/
@Bean(autowire = Autowire.BY_TYPE)
public ResponseHighlighterInterceptor responseHighlighterInterceptor() {
ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor();
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IServerInterceptor subscriptionSecurityInterceptor() {
SubscriptionsRequireManualActivationInterceptorR4 retVal = new SubscriptionsRequireManualActivationInterceptorR4();
return retVal;
}
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);
return retVal;
}
}

@ -285,6 +285,7 @@ public abstract class BaseConfig {
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
//TODO: Consider moving the jpa.dao.lastn.entity.* classes into jpa.entity at some point.
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity", "ca.uhn.fhir.jpa.dao.lastn.entity");
theFactory.setPersistenceProvider(new HibernatePersistenceProvider());
}

@ -8,6 +8,7 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3;
import ca.uhn.fhir.jpa.dao.lastn.ObservationLastNIndexPersistDstu3Svc;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
@ -17,10 +18,7 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@ -131,4 +129,9 @@ public class BaseDstu3Config extends BaseConfigDstu3Plus {
return new TermReadSvcDstu3();
}
@Bean
public ObservationLastNIndexPersistDstu3Svc observationLastNIndexpersistSvc() {
return new ObservationLastNIndexPersistDstu3Svc();
}
}

@ -7,7 +7,7 @@ import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.lastn.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.dao.lastn.ObservationLastNIndexPersistR4Svc;
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
@ -18,10 +18,7 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -136,10 +133,8 @@ public class BaseR4Config extends BaseConfigDstu3Plus {
}
@Bean
public ObservationLastNIndexPersistSvc observationLastNIndexpersistSvc() {
return new ObservationLastNIndexPersistSvc();
public ObservationLastNIndexPersistR4Svc observationLastNIndexpersistSvc() {
return new ObservationLastNIndexPersistR4Svc();
}
}

@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.lastn.ObservationLastNIndexPersistR5Svc;
import ca.uhn.fhir.jpa.dao.r5.TransactionProcessorVersionAdapterR5;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR5;
@ -17,11 +18,8 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -135,4 +133,9 @@ public class BaseR5Config extends BaseConfigDstu3Plus {
return new TermReadSvcR5();
}
@Bean
public ObservationLastNIndexPersistR5Svc observationLastNIndexpersistSvc() {
return new ObservationLastNIndexPersistR5Svc();
}
}

@ -0,0 +1,34 @@
package ca.uhn.fhir.jpa.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> {
IBundleProvider observationsLastN(SearchParameterMap paramMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse);
}

@ -45,6 +45,7 @@ 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;
@ -110,6 +111,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -152,6 +154,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
@ -314,22 +318,51 @@ public class SearchBuilder implements ISearchBuilder {
}
/*
* Fulltext search
* Fulltext search and lastn
*/
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.containsKey(Constants.PARAM_CONTENT) || myParams.containsKey(Constants.PARAM_TEXT) || myParams.isLastN()) {
List<ResourcePersistentId> lastnPids = new ArrayList<>();
List<ResourcePersistentId> fullTextSearchPids = new ArrayList<>();
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) {
fullTextSearchPids = myFulltextSearchSvc.everything(myResourceName, myParams, theRequest);
} else {
fullTextSearchPids = myFulltextSearchSvc.search(myResourceName, myParams);
}
} else {
if (myIElasticsearchSvc == null) {
if (myParams.isLastN()) {
throw new InvalidRequestException("LastN operation is not enabled on this service, can not process this request");
}
}
if (myParams.isLastN()) {
lastnPids = myIElasticsearchSvc.executeLastN(myParams, theRequest, myIdHelperService);
}
}
//
List<ResourcePersistentId> pids;
if (myParams.getEverythingMode() != null) {
pids = myFulltextSearchSvc.everything(myResourceName, myParams, theRequest);
if (fullTextSearchPids.isEmpty()) {
pids = lastnPids;
} else if (lastnPids.isEmpty()) {
pids = fullTextSearchPids;
} else {
pids = myFulltextSearchSvc.search(myResourceName, myParams);
// Intersection of the fullTextSearchPids and lastnPids
Set<ResourcePersistentId> pidIntersection = fullTextSearchPids.stream()
.distinct()
.filter(lastnPids::contains)
.collect(Collectors.toSet());
pids = new ArrayList<>(pidIntersection);
}
if (pids.isEmpty()) {
// Will never match

@ -2,8 +2,16 @@ package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.dao.lastn.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);
}

@ -0,0 +1,113 @@
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.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.dao.lastn.ObservationLastNIndexPersistDstu3Svc;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringParam;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.dstu3.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
public class FhirResourceDaoObservationDstu3 extends BaseHapiFhirResourceDao<Observation> implements IFhirResourceDaoObservation<Observation> {
@Autowired
ObservationLastNIndexPersistDstu3Svc myObservationLastNIndexPersistDstu3Svc;
private IBundleProvider doLastNOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
if (theContent != null) {
paramMap.add(Constants.PARAM_CONTENT, theContent);
}
if (theNarrative != null) {
paramMap.add(Constants.PARAM_TEXT, theNarrative);
}
paramMap.setIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive()));
paramMap.setEverythingMode(theId != null ? EverythingModeEnum.PATIENT_INSTANCE : EverythingModeEnum.PATIENT_TYPE);
paramMap.setSort(theSort);
paramMap.setLastUpdated(theLastUpdated);
if (theId != null) {
paramMap.add("_id", new StringParam(theId.getIdPart()));
}
if (!isPagingProviderDatabaseBacked(theRequest)) {
paramMap.setLoadSynchronous(true);
}
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest);
}
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails);
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
if(thePerformIndexing) {
// Update indexes here for LastN operation.
Observation observation = (Observation)theResource;
Collection<ResourceLink> myResourceLinks = retVal.getResourceLinks();
Long subjectID = null;
for (ResourceLink resourceLink : myResourceLinks) {
if(resourceLink.getSourcePath().equals("Observation.subject")) {
subjectID = resourceLink.getTargetResourcePid();
}
}
if (subjectID != null) {
myObservationLastNIndexPersistDstu3Svc.indexObservation(observation, subjectID.toString());
} else {
myObservationLastNIndexPersistDstu3Svc.indexObservation(observation);
}
}
return retVal;
}
}

@ -0,0 +1,87 @@
package ca.uhn.fhir.jpa.dao.lastn;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodeableConceptSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodingSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedSearchParamLastNDao;
import ca.uhn.fhir.jpa.dao.lastn.entity.*;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ObservationLastNIndexPersistDstu3Svc {
@Autowired
IObservationIndexedSearchParamLastNDao myResourceIndexedObservationLastNDao;
@Autowired
IObservationIndexedCodeCodeableConceptSearchParamDao myObservationIndexedCodeableConceptSearchParamDao;
@Autowired
IObservationIndexedCodeCodingSearchParamDao myObservationIndexedCodeCodingSearchParamDao;
// TODO: Change theSubjectId to be a Long
public void indexObservation(Observation theObservation, String theSubjectId) {
ObservationIndexedSearchParamLastNEntity indexedObservation = new ObservationIndexedSearchParamLastNEntity();
String resourcePID = theObservation.getIdElement().getIdPart();
indexedObservation.setIdentifier(resourcePID);
indexedObservation.setSubject(theSubjectId);
Date effectiveDtm = theObservation.getEffectiveDateTimeType().getValue();
indexedObservation.setEffectiveDtm(effectiveDtm);
// Build CodeableConcept entities for Observation.Category
Set<ObservationIndexedCategoryCodeableConceptEntity> categoryConcepts = new HashSet<>();
for(CodeableConcept categoryCodeableConcept : theObservation.getCategory()) {
// Build Coding entities for each category CodeableConcept
Set<ObservationIndexedCategoryCodingEntity> categoryCodingEntities = new HashSet<>();
ObservationIndexedCategoryCodeableConceptEntity categoryCodeableConceptEntity = new ObservationIndexedCategoryCodeableConceptEntity(categoryCodeableConcept.getText());
for(Coding categoryCoding : categoryCodeableConcept.getCoding()){
categoryCodingEntities.add(new ObservationIndexedCategoryCodingEntity(categoryCoding.getSystem(), categoryCoding.getCode(), categoryCoding.getDisplay()));
}
categoryCodeableConceptEntity.setObservationIndexedCategoryCodingEntitySet(categoryCodingEntities);
categoryConcepts.add(categoryCodeableConceptEntity);
}
indexedObservation.setCategoryCodeableConcepts(categoryConcepts);
// Build CodeableConcept entity for Observation.Code.
CodeableConcept codeCodeableConcept = theObservation.getCode();
String observationCodeNormalizedId = null;
// Determine if a Normalized ID was created previously for Observation Code
for (Coding codeCoding : codeCodeableConcept.getCoding()) {
if (codeCoding.hasCode() && codeCoding.hasSystem()) {
observationCodeNormalizedId = myObservationIndexedCodeCodingSearchParamDao.findForCodeAndSystem(codeCoding.getCode(), codeCoding.getSystem());
} else {
observationCodeNormalizedId = myObservationIndexedCodeCodingSearchParamDao.findForDisplay(codeCoding.getDisplay());
}
}
// Generate a new a normalized ID if necessary
if (observationCodeNormalizedId == null) {
observationCodeNormalizedId = UUID.randomUUID().toString();
}
// Create/update normalized Observation Code index record
ObservationIndexedCodeCodeableConceptEntity codeableConceptField = new ObservationIndexedCodeCodeableConceptEntity(codeCodeableConcept.getText(), observationCodeNormalizedId);
for (Coding codeCoding : codeCodeableConcept.getCoding()) {
codeableConceptField.addCoding(new ObservationIndexedCodeCodingEntity(codeCoding.getSystem(), codeCoding.getCode(), codeCoding.getDisplay(), observationCodeNormalizedId));
}
myObservationIndexedCodeableConceptSearchParamDao.save(codeableConceptField);
indexedObservation.setObservationCode(codeableConceptField);
indexedObservation.setCodeNormalizedId(observationCodeNormalizedId);
myResourceIndexedObservationLastNDao.save(indexedObservation);
}
// TODO: Remove this once Unit tests are updated.
public void indexObservation(Observation theObservation) {
String subjectId = "Patient/" + theObservation.getSubject().getReference();
indexObservation(theObservation, subjectId);
}
}

@ -0,0 +1,88 @@
package ca.uhn.fhir.jpa.dao.lastn;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodeableConceptSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodingSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedSearchParamLastNDao;
import ca.uhn.fhir.jpa.dao.lastn.entity.*;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ObservationLastNIndexPersistR4Svc {
@Autowired
IObservationIndexedSearchParamLastNDao myResourceIndexedObservationLastNDao;
@Autowired
IObservationIndexedCodeCodeableConceptSearchParamDao myObservationIndexedCodeableConceptSearchParamDao;
@Autowired
IObservationIndexedCodeCodingSearchParamDao myObservationIndexedCodeCodingSearchParamDao;
// TODO: Change theSubjectId to be a Long
public void indexObservation(Observation theObservation, String theSubjectId) {
ObservationIndexedSearchParamLastNEntity indexedObservation = new ObservationIndexedSearchParamLastNEntity();
String resourcePID = theObservation.getIdElement().getIdPart();
indexedObservation.setIdentifier(resourcePID);
indexedObservation.setSubject(theSubjectId);
Date effectiveDtm = theObservation.getEffectiveDateTimeType().getValue();
indexedObservation.setEffectiveDtm(effectiveDtm);
// Build CodeableConcept entities for Observation.Category
Set<ObservationIndexedCategoryCodeableConceptEntity> categoryConcepts = new HashSet<>();
for(CodeableConcept categoryCodeableConcept : theObservation.getCategory()) {
// Build Coding entities for each category CodeableConcept
Set<ObservationIndexedCategoryCodingEntity> categoryCodingEntities = new HashSet<>();
ObservationIndexedCategoryCodeableConceptEntity categoryCodeableConceptEntity = new ObservationIndexedCategoryCodeableConceptEntity(categoryCodeableConcept.getText());
for(Coding categoryCoding : categoryCodeableConcept.getCoding()){
categoryCodingEntities.add(new ObservationIndexedCategoryCodingEntity(categoryCoding.getSystem(), categoryCoding.getCode(), categoryCoding.getDisplay()));
}
categoryCodeableConceptEntity.setObservationIndexedCategoryCodingEntitySet(categoryCodingEntities);
categoryConcepts.add(categoryCodeableConceptEntity);
}
indexedObservation.setCategoryCodeableConcepts(categoryConcepts);
// Build CodeableConcept entity for Observation.Code.
CodeableConcept codeCodeableConcept = theObservation.getCode();
String observationCodeNormalizedId = null;
// Determine if a Normalized ID was created previously for Observation Code
for (Coding codeCoding : codeCodeableConcept.getCoding()) {
if (codeCoding.hasCode() && codeCoding.hasSystem()) {
observationCodeNormalizedId = myObservationIndexedCodeCodingSearchParamDao.findForCodeAndSystem(codeCoding.getCode(), codeCoding.getSystem());
} else {
observationCodeNormalizedId = myObservationIndexedCodeCodingSearchParamDao.findForDisplay(codeCoding.getDisplay());
}
}
// Generate a new a normalized ID if necessary
if (observationCodeNormalizedId == null) {
observationCodeNormalizedId = UUID.randomUUID().toString();
}
// Create/update normalized Observation Code index record
ObservationIndexedCodeCodeableConceptEntity codeableConceptField = new ObservationIndexedCodeCodeableConceptEntity(codeCodeableConcept.getText(), observationCodeNormalizedId);
for (Coding codeCoding : codeCodeableConcept.getCoding()) {
codeableConceptField.addCoding(new ObservationIndexedCodeCodingEntity(codeCoding.getSystem(), codeCoding.getCode(), codeCoding.getDisplay(), observationCodeNormalizedId));
}
myObservationIndexedCodeableConceptSearchParamDao.save(codeableConceptField);
codeableConceptField = myObservationIndexedCodeableConceptSearchParamDao.findByCodeableConceptId(observationCodeNormalizedId);
indexedObservation.setObservationCode(codeableConceptField);
indexedObservation.setCodeNormalizedId(observationCodeNormalizedId);
myResourceIndexedObservationLastNDao.save(indexedObservation);
}
// TODO: Remove this once Unit tests are updated.
public void indexObservation(Observation theObservation) {
String subjectId = "Patient/" + theObservation.getSubject().getReference();
indexObservation(theObservation, subjectId);
}
}

@ -0,0 +1,87 @@
package ca.uhn.fhir.jpa.dao.lastn;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodeableConceptSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodingSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedSearchParamLastNDao;
import ca.uhn.fhir.jpa.dao.lastn.entity.*;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ObservationLastNIndexPersistR5Svc {
@Autowired
IObservationIndexedSearchParamLastNDao myResourceIndexedObservationLastNDao;
@Autowired
IObservationIndexedCodeCodeableConceptSearchParamDao myObservationIndexedCodeableConceptSearchParamDao;
@Autowired
IObservationIndexedCodeCodingSearchParamDao myObservationIndexedCodeCodingSearchParamDao;
// TODO: Change theSubjectId to be a Long
public void indexObservation(Observation theObservation, String theSubjectId) {
ObservationIndexedSearchParamLastNEntity indexedObservation = new ObservationIndexedSearchParamLastNEntity();
String resourcePID = theObservation.getIdElement().getIdPart();
indexedObservation.setIdentifier(resourcePID);
indexedObservation.setSubject(theSubjectId);
Date effectiveDtm = theObservation.getEffectiveDateTimeType().getValue();
indexedObservation.setEffectiveDtm(effectiveDtm);
// Build CodeableConcept entities for Observation.Category
Set<ObservationIndexedCategoryCodeableConceptEntity> categoryConcepts = new HashSet<>();
for(CodeableConcept categoryCodeableConcept : theObservation.getCategory()) {
// Build Coding entities for each category CodeableConcept
Set<ObservationIndexedCategoryCodingEntity> categoryCodingEntities = new HashSet<>();
ObservationIndexedCategoryCodeableConceptEntity categoryCodeableConceptEntity = new ObservationIndexedCategoryCodeableConceptEntity(categoryCodeableConcept.getText());
for(Coding categoryCoding : categoryCodeableConcept.getCoding()){
categoryCodingEntities.add(new ObservationIndexedCategoryCodingEntity(categoryCoding.getSystem(), categoryCoding.getCode(), categoryCoding.getDisplay()));
}
categoryCodeableConceptEntity.setObservationIndexedCategoryCodingEntitySet(categoryCodingEntities);
categoryConcepts.add(categoryCodeableConceptEntity);
}
indexedObservation.setCategoryCodeableConcepts(categoryConcepts);
// Build CodeableConcept entity for Observation.Code.
CodeableConcept codeCodeableConcept = theObservation.getCode();
String observationCodeNormalizedId = null;
// Determine if a Normalized ID was created previously for Observation Code
for (Coding codeCoding : codeCodeableConcept.getCoding()) {
if (codeCoding.hasCode() && codeCoding.hasSystem()) {
observationCodeNormalizedId = myObservationIndexedCodeCodingSearchParamDao.findForCodeAndSystem(codeCoding.getCode(), codeCoding.getSystem());
} else {
observationCodeNormalizedId = myObservationIndexedCodeCodingSearchParamDao.findForDisplay(codeCoding.getDisplay());
}
}
// Generate a new a normalized ID if necessary
if (observationCodeNormalizedId == null) {
observationCodeNormalizedId = UUID.randomUUID().toString();
}
// Create/update normalized Observation Code index record
ObservationIndexedCodeCodeableConceptEntity codeableConceptField = new ObservationIndexedCodeCodeableConceptEntity(codeCodeableConcept.getText(), observationCodeNormalizedId);
for (Coding codeCoding : codeCodeableConcept.getCoding()) {
codeableConceptField.addCoding(new ObservationIndexedCodeCodingEntity(codeCoding.getSystem(), codeCoding.getCode(), codeCoding.getDisplay(), observationCodeNormalizedId));
}
myObservationIndexedCodeableConceptSearchParamDao.save(codeableConceptField);
indexedObservation.setObservationCode(codeableConceptField);
indexedObservation.setCodeNormalizedId(observationCodeNormalizedId);
myResourceIndexedObservationLastNDao.save(indexedObservation);
}
// TODO: Remove this once Unit tests are updated.
public void indexObservation(Observation theObservation) {
String subjectId = "Patient/" + theObservation.getSubject().getReference();
indexObservation(theObservation, subjectId);
}
}

@ -12,7 +12,7 @@ import org.springframework.stereotype.Component;
import java.util.*;
@Component
//@Component
public class ObservationLastNIndexPersistSvc {
@Autowired

@ -25,8 +25,11 @@ public class ObservationIndexedCodeCodeableConceptEntity {
private String myCodeableConceptText;
@IndexedEmbedded(depth=2, prefix = "coding")
@OneToMany(mappedBy = "myCodeableConceptId", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<ObservationIndexedCodeCodingEntity> myObservationIndexedCodeCodingEntitySet;
// @OneToMany(mappedBy = "myCodeableConceptId", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@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 Set<ObservationIndexedCodeCodingEntity> myObservationIndexedCodeCodingEntitySet;
private ObservationIndexedCodeCodingEntity myObservationIndexedCodeCodingEntity;
public ObservationIndexedCodeCodeableConceptEntity() {
@ -38,10 +41,11 @@ public class ObservationIndexedCodeCodeableConceptEntity {
}
public void addCoding(ObservationIndexedCodeCodingEntity theObservationIndexedCodeCodingEntity) {
if (myObservationIndexedCodeCodingEntitySet == null) {
myObservationIndexedCodeCodingEntitySet = new HashSet<>();
}
myObservationIndexedCodeCodingEntitySet.add(theObservationIndexedCodeCodingEntity);
// if (myObservationIndexedCodeCodingEntitySet == null) {
// myObservationIndexedCodeCodingEntitySet = new HashSet<>();
// }
// myObservationIndexedCodeCodingEntitySet.add(theObservationIndexedCodeCodingEntity);
myObservationIndexedCodeCodingEntity = theObservationIndexedCodeCodingEntity;
}
public String getCodeableConceptId() {

@ -11,11 +11,13 @@ import javax.persistence.*;
@Table(name = "HFJ_SPIDX_LASTN_CODING")
public class ObservationIndexedCodeCodingEntity {
@Id
@SequenceGenerator(name = "SEQ_CODING_FIELD", sequenceName = "SEQ_CODING_FIELD")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CODING_FIELD")
private Long myId;
// TODO: Fix this to allow multiple codings for observation code
// @Id
// @SequenceGenerator(name = "SEQ_CODING_FIELD", sequenceName = "SEQ_CODING_FIELD")
// @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CODING_FIELD")
// private Long myId;
@Id
@Column(name="CODEABLE_CONCEPT_ID")
private String myCodeableConceptId;

@ -21,7 +21,11 @@ package ca.uhn.fhir.jpa.dao.r4;
*/
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.dao.lastn.ObservationLastNIndexPersistR4Svc;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
@ -38,42 +42,26 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDao<Observation> {
public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDao<Observation> implements IFhirResourceDaoObservation<Observation> {
private IBundleProvider doLastNOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
if (theContent != null) {
paramMap.add(Constants.PARAM_CONTENT, theContent);
}
if (theNarrative != null) {
paramMap.add(Constants.PARAM_TEXT, theNarrative);
}
paramMap.setIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive()));
paramMap.setEverythingMode(theId != null ? EverythingModeEnum.PATIENT_INSTANCE : EverythingModeEnum.PATIENT_TYPE);
paramMap.setSort(theSort);
paramMap.setLastUpdated(theLastUpdated);
if (theId != null) {
paramMap.add("_id", new StringParam(theId.getIdPart()));
}
if (!isPagingProviderDatabaseBacked(theRequest)) {
paramMap.setLoadSynchronous(true);
}
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest);
@Autowired
ObservationLastNIndexPersistR4Svc myObservationLastNIndexPersistR4Svc;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails);
}
public IBundleProvider observationsLastN(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
return doLastNOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails);
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
@ -83,10 +71,21 @@ public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDao<Observ
if(thePerformIndexing) {
// Update indexes here for LastN operation.
Observation observation = (Observation)theResource;
Collection<ResourceLink> myResourceLinks = retVal.getResourceLinks();
Long subjectID = null;
for (ResourceLink resourceLink : myResourceLinks) {
if(resourceLink.getSourcePath().equals("Observation.subject")) {
subjectID = resourceLink.getTargetResourcePid();
}
}
if (subjectID != null) {
myObservationLastNIndexPersistR4Svc.indexObservation(observation, subjectID.toString());
} else {
myObservationLastNIndexPersistR4Svc.indexObservation(observation);
}
}
return retVal;
}
}
}

@ -0,0 +1,113 @@
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.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.dao.lastn.ObservationLastNIndexPersistR5Svc;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringParam;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
public class FhirResourceDaoObservationR5 extends BaseHapiFhirResourceDao<Observation> implements IFhirResourceDaoObservation<Observation> {
@Autowired
ObservationLastNIndexPersistR5Svc myObservationLastNIndexPersistR5Svc;
private IBundleProvider doLastNOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
if (theContent != null) {
paramMap.add(Constants.PARAM_CONTENT, theContent);
}
if (theNarrative != null) {
paramMap.add(Constants.PARAM_TEXT, theNarrative);
}
paramMap.setIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive()));
paramMap.setEverythingMode(theId != null ? EverythingModeEnum.PATIENT_INSTANCE : EverythingModeEnum.PATIENT_TYPE);
paramMap.setSort(theSort);
paramMap.setLastUpdated(theLastUpdated);
if (theId != null) {
paramMap.add("_id", new StringParam(theId.getIdPart()));
}
if (!isPagingProviderDatabaseBacked(theRequest)) {
paramMap.setLoadSynchronous(true);
}
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest);
}
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails);
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
if(thePerformIndexing) {
// Update indexes here for LastN operation.
Observation observation = (Observation)theResource;
Collection<ResourceLink> myResourceLinks = retVal.getResourceLinks();
Long subjectID = null;
for (ResourceLink resourceLink : myResourceLinks) {
if(resourceLink.getSourcePath().equals("Observation.subject")) {
subjectID = resourceLink.getTargetResourcePid();
}
}
if (subjectID != null) {
myObservationLastNIndexPersistR5Svc.indexObservation(observation, subjectID.toString());
} else {
myObservationLastNIndexPersistR5Svc.indexObservation(observation);
}
}
return retVal;
}
}

@ -0,0 +1,148 @@
package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import java.util.Set;
/*
* #%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 BaseJpaResourceProviderObservationDstu2 extends JpaResourceProviderDstu2<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(shortDefinition="Search the contents of the resource's data using a filter")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_FILTER)
StringAndListParam theFtFilter,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_TEXT)
StringAndListParam theFtText,
@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="Obtained date/time. If the obtained element is a period, a date that falls in the period")
@OperationParam(name="date")
DateRangeParam theDate,
@Description(shortDefinition="The maximum number of observations to return for each each observation code")
@OperationParam(name="max", max=1, min=0)
NumberParam theMax,
@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,
@IncludeParam(reverse=true)
Set<Include> theRevIncludes,
@Description(shortDefinition="Only return resources which were last updated as specified by the given range")
@OperationParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Observation:based-on",
"Observation:derived-from",
"Observation:device",
"Observation:encounter",
"Observation:focus",
"Observation:has-member",
"Observation:part-of",
"Observation:patient",
"Observation:performer",
"Observation:specimen",
"Observation:subject",
"*"
})
Set<Include> theIncludes,
@Sort
SortSpec theSort,
@ca.uhn.fhir.rest.annotation.Count
Integer theCount,
SummaryEnum theSummaryMode,
SearchTotalModeEnum theSearchTotalMode
) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_FILTER, theFtFilter);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, theFtContent);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, theFtText);
paramMap.add("category", theCategory);
paramMap.add("code", theCode);
paramMap.add("date", theDate);
paramMap.add("max", theMax);
paramMap.add("patient", thePatient);
paramMap.add("subject", theSubject);
paramMap.setRevIncludes(theRevIncludes);
paramMap.setLastUpdated(theLastUpdated);
paramMap.setIncludes(theIncludes);
paramMap.setLastN(true);
paramMap.setSort(theSort);
paramMap.setCount(theCount);
paramMap.setSummaryMode(theSummaryMode);
paramMap.setSearchTotalMode(theSearchTotalMode);
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally {
endRequest(theServletRequest);
}
}
}

@ -0,0 +1,148 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
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.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.dstu3.model.Observation;
import java.util.Set;
/*
* #%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(shortDefinition="Search the contents of the resource's data using a filter")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_FILTER)
StringAndListParam theFtFilter,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_TEXT)
StringAndListParam theFtText,
@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="Obtained date/time. If the obtained element is a period, a date that falls in the period")
@OperationParam(name="date")
DateRangeParam theDate,
@Description(shortDefinition="The maximum number of observations to return for each each observation code")
@OperationParam(name="max", max=1, min=0)
NumberParam theMax,
@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,
@IncludeParam(reverse=true)
Set<Include> theRevIncludes,
@Description(shortDefinition="Only return resources which were last updated as specified by the given range")
@OperationParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Observation:based-on",
"Observation:derived-from",
"Observation:device",
"Observation:encounter",
"Observation:focus",
"Observation:has-member",
"Observation:part-of",
"Observation:patient",
"Observation:performer",
"Observation:specimen",
"Observation:subject",
"*"
})
Set<Include> theIncludes,
@Sort
SortSpec theSort,
@ca.uhn.fhir.rest.annotation.Count
Integer theCount,
SummaryEnum theSummaryMode,
SearchTotalModeEnum theSearchTotalMode
) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_FILTER, theFtFilter);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, theFtContent);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, theFtText);
paramMap.add("category", theCategory);
paramMap.add("code", theCode);
paramMap.add("date", theDate);
paramMap.add("max", theMax);
paramMap.add("patient", thePatient);
paramMap.add("subject", theSubject);
paramMap.setRevIncludes(theRevIncludes);
paramMap.setLastUpdated(theLastUpdated);
paramMap.setIncludes(theIncludes);
paramMap.setLastN(true);
paramMap.setSort(theSort);
paramMap.setCount(theCount);
paramMap.setSummaryMode(theSummaryMode);
paramMap.setSearchTotalMode(theSearchTotalMode);
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally {
endRequest(theServletRequest);
}
}
}

@ -0,0 +1,149 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
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.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.r4.model.*;
import java.util.Set;
/*
* #%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(shortDefinition="Search the contents of the resource's data using a filter")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_FILTER)
StringAndListParam theFtFilter,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_TEXT)
StringAndListParam theFtText,
@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="Obtained date/time. If the obtained element is a period, a date that falls in the period")
@OperationParam(name="date")
DateRangeParam theDate,
// @Description(shortDefinition="The maximum number of observations to return for each each observation code")
// @OperationParam(name="max", max=1, min=0)
// NumberParam theMax,
@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,
@IncludeParam(reverse=true)
Set<Include> theRevIncludes,
@Description(shortDefinition="Only return resources which were last updated as specified by the given range")
@OperationParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Observation:based-on",
"Observation:derived-from",
"Observation:device",
"Observation:encounter",
"Observation:focus",
"Observation:has-member",
"Observation:part-of",
"Observation:patient",
"Observation:performer",
"Observation:specimen",
"Observation:subject",
"*"
})
Set<Include> theIncludes,
@Sort
SortSpec theSort,
@ca.uhn.fhir.rest.annotation.Count
Integer theCount,
SummaryEnum theSummaryMode,
SearchTotalModeEnum theSearchTotalMode
) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_FILTER, theFtFilter);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, theFtContent);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, theFtText);
paramMap.add("category", theCategory);
paramMap.add("code", theCode);
paramMap.add("date", theDate);
// paramMap.add("max", theMax);
paramMap.add("patient", thePatient);
paramMap.add("subject", theSubject);
paramMap.setRevIncludes(theRevIncludes);
paramMap.setLastUpdated(theLastUpdated);
paramMap.setIncludes(theIncludes);
paramMap.setLastN(true);
paramMap.setSort(theSort);
paramMap.setCount(theCount);
paramMap.setSummaryMode(theSummaryMode);
paramMap.setSearchTotalMode(theSearchTotalMode);
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally {
endRequest(theServletRequest);
}
}
}

@ -0,0 +1,148 @@
package ca.uhn.fhir.jpa.provider.r5;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
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.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.r5.model.Observation;
import java.util.Set;
/*
* #%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(shortDefinition="Search the contents of the resource's data using a filter")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_FILTER)
StringAndListParam theFtFilter,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_TEXT)
StringAndListParam theFtText,
@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="Obtained date/time. If the obtained element is a period, a date that falls in the period")
@OperationParam(name="date")
DateRangeParam theDate,
@Description(shortDefinition="The maximum number of observations to return for each each observation code")
@OperationParam(name="max", max=1, min=0)
NumberParam theMax,
@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,
@IncludeParam(reverse=true)
Set<Include> theRevIncludes,
@Description(shortDefinition="Only return resources which were last updated as specified by the given range")
@OperationParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Observation:based-on",
"Observation:derived-from",
"Observation:device",
"Observation:encounter",
"Observation:focus",
"Observation:has-member",
"Observation:part-of",
"Observation:patient",
"Observation:performer",
"Observation:specimen",
"Observation:subject",
"*"
})
Set<Include> theIncludes,
@Sort
SortSpec theSort,
@ca.uhn.fhir.rest.annotation.Count
Integer theCount,
SummaryEnum theSummaryMode,
SearchTotalModeEnum theSearchTotalMode
) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_FILTER, theFtFilter);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, theFtContent);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, theFtText);
paramMap.add("category", theCategory);
paramMap.add("code", theCode);
paramMap.add("date", theDate);
paramMap.add("max", theMax);
paramMap.add("patient", thePatient);
paramMap.add("subject", theSubject);
paramMap.setRevIncludes(theRevIncludes);
paramMap.setLastUpdated(theLastUpdated);
paramMap.setIncludes(theIncludes);
paramMap.setLastN(true);
paramMap.setSort(theSort);
paramMap.setCount(theCount);
paramMap.setSummaryMode(theSummaryMode);
paramMap.setSearchTotalMode(theSearchTotalMode);
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally {
endRequest(theServletRequest);
}
}
}

@ -1,25 +1,28 @@
package ca.uhn.fhir.jpa.search.lastn;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import ca.uhn.fhir.jpa.search.lastn.util.CodeSystemHash;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
//import org.assertj.core.util.VisibleForTesting;
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.delete.DeleteIndexRequest;
import org.shadehapi.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.shadehapi.elasticsearch.action.DocWriteResponse;
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.action.support.master.AcknowledgedResponse;
import org.shadehapi.elasticsearch.client.RequestOptions;
import org.shadehapi.elasticsearch.client.RestHighLevelClient;
import org.shadehapi.elasticsearch.common.xcontent.XContentType;
@ -39,7 +42,6 @@ import org.shadehapi.elasticsearch.search.aggregations.metrics.tophits.ParsedTop
import org.shadehapi.elasticsearch.search.aggregations.support.ValueType;
import org.shadehapi.elasticsearch.search.builder.SearchSourceBuilder;
import org.shadehapi.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
@ -47,7 +49,6 @@ import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
//@Component
public class ElasticsearchSvcImpl implements IElasticsearchSvc {
RestHighLevelClient myRestHighLevelClient;
@ -56,18 +57,17 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
public ElasticsearchSvcImpl(String theHostname, int thePort, String theUsername, String thePassword) {
myRestHighLevelClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(theHostname, thePort, theUsername,thePassword);
myRestHighLevelClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(theHostname, thePort, theUsername,thePassword);
try {
createObservationIndexIfMissing();
createCodeIndexIfMissing();
} catch (IOException theE) {
throw new RuntimeException("Failed to create document index", theE);
}
}
try {
createObservationIndexIfMissing();
createCodeIndexIfMissing();
} catch (IOException theE) {
throw new RuntimeException("Failed to create document index", theE);
}
}
public void createObservationIndexIfMissing() throws IOException {
private void createObservationIndexIfMissing() throws IOException {
if(indexExists(IndexConstants.OBSERVATION_INDEX)) {
return;
}
@ -129,7 +129,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
}
public void createCodeIndexIfMissing() throws IOException {
private void createCodeIndexIfMissing() throws IOException {
if(indexExists(IndexConstants.CODE_INDEX)) {
return;
}
@ -166,7 +166,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
}
public boolean createIndex(String theIndexName, String theMapping) throws IOException {
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);
@ -174,12 +174,11 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
}
public boolean performIndex(String theIndexName, String theDocumentId, String theIndexDocument, String theDocumentType) throws IOException {
boolean performIndex(String theIndexName, String theDocumentId, String theIndexDocument, String theDocumentType) throws IOException {
IndexResponse indexResponse = myRestHighLevelClient.index(createIndexRequest(theIndexName, theDocumentId,theIndexDocument,theDocumentType),
RequestOptions.DEFAULT);
IndexResponse indexResponse = myRestHighLevelClient.index(createIndexRequest(theIndexName, theDocumentId,theIndexDocument,theDocumentType),
RequestOptions.DEFAULT);
return (indexResponse.getResult() == DocWriteResponse.Result.CREATED) || (indexResponse.getResult() == DocWriteResponse.Result.UPDATED);
return (indexResponse.getResult() == DocWriteResponse.Result.CREATED) || (indexResponse.getResult() == DocWriteResponse.Result.UPDATED);
}
private IndexRequest createIndexRequest(String theIndexName, String theDocumentId, String theObservationDocument, String theDocumentType) {
@ -191,12 +190,31 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return request;
}
public boolean indexExists(String theIndexName) throws IOException {
private boolean indexExists(String theIndexName) throws IOException {
GetIndexRequest request = new GetIndexRequest();
request.indices(theIndexName);
return myRestHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
}
@Override
public List<ResourcePersistentId> executeLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, IdHelperService theIdHelperService) {
Integer myMaxObservationsPerCode = 1;
String[] maxCountParams = theRequestDetails.getParameters().get("map");
if (maxCountParams != null && maxCountParams.length > 0) {
myMaxObservationsPerCode = Integer.valueOf(maxCountParams[0]);
}
SearchRequest myLastNRequest = buildObservationCompositeSearchRequest(10000, theSearchParameterMap, myMaxObservationsPerCode);
SearchResponse lastnResponse = null;
try {
lastnResponse = executeSearchRequest(myLastNRequest);
List<ResourcePersistentId> observationIds = buildObservationIdList(lastnResponse, theIdHelperService);
return observationIds;
} catch (IOException theE) {
throw new InvalidRequestException("Unable to execute LastN request", theE);
}
}
SearchRequest buildObservationCodesSearchRequest(int theMaxResultSetSize) {
SearchRequest searchRequest = new SearchRequest(IndexConstants.CODE_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
@ -236,15 +254,15 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
}
public SearchResponse executeSearchRequest(SearchRequest searchRequest) throws IOException {
return myRestHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
return myRestHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
}
public List<ObservationJson> buildObservationCompositeResults(SearchResponse theSearchResponse) throws IOException {
Aggregations responseAggregations = theSearchResponse.getAggregations();
ParsedComposite aggregatedSubjects = responseAggregations.get("group_by_subject");
List<ParsedComposite.ParsedBucket> subjectBuckets = aggregatedSubjects.getBuckets();
List<ObservationJson> codes = new ArrayList<>();
for(ParsedComposite.ParsedBucket subjectBucket : subjectBuckets) {
Aggregations responseAggregations = theSearchResponse.getAggregations();
ParsedComposite aggregatedSubjects = responseAggregations.get("group_by_subject");
List<ParsedComposite.ParsedBucket> subjectBuckets = aggregatedSubjects.getBuckets();
List<ObservationJson> codes = new ArrayList<>();
for(ParsedComposite.ParsedBucket subjectBucket : subjectBuckets) {
Aggregations observationCodeAggregations = subjectBucket.getAggregations();
ParsedTerms aggregatedObservationCodes = observationCodeAggregations.get("group_by_code");
List<? extends Terms.Bucket> observationCodeBuckets = aggregatedObservationCodes.getBuckets();
@ -262,7 +280,30 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return codes;
}
public List<ObservationJson> buildObservationTermsResults(SearchResponse theSearchResponse) throws IOException {
private List<ResourcePersistentId> buildObservationIdList(SearchResponse theSearchResponse, IdHelperService theIdHelperService) throws IOException {
Aggregations responseAggregations = theSearchResponse.getAggregations();
ParsedComposite aggregatedSubjects = responseAggregations.get("group_by_subject");
List<ParsedComposite.ParsedBucket> subjectBuckets = aggregatedSubjects.getBuckets();
List<ResourcePersistentId> myObservationIds = new ArrayList<>();
for(ParsedComposite.ParsedBucket subjectBucket : subjectBuckets) {
Aggregations observationCodeAggregations = subjectBucket.getAggregations();ParsedTerms aggregatedObservationCodes = observationCodeAggregations.get("group_by_code");
List<? extends Terms.Bucket> observationCodeBuckets = aggregatedObservationCodes.getBuckets();
for (Terms.Bucket observationCodeBucket : observationCodeBuckets) {
Aggregations topHitObservationCodes = observationCodeBucket.getAggregations();
ParsedTopHits parsedTopHits = topHitObservationCodes.get("most_recent_effective");
SearchHit[] topHits = parsedTopHits.getHits().getHits();
for (SearchHit topHit : topHits) {
String sources = topHit.getSourceAsString();
ObservationJson code = objectMapper.readValue(sources, ObservationJson.class);
myObservationIds.add(theIdHelperService.resolveResourcePersistentIds("Observation", code.getIdentifier()));
}
}
}
return myObservationIds;
}
public List<ObservationJson> buildObservationTermsResults(SearchResponse theSearchResponse) throws IOException {
Aggregations responseAggregations = theSearchResponse.getAggregations();
ParsedTerms aggregatedSubjects = responseAggregations.get("group_by_subject");
List<? extends Terms.Bucket> subjectBuckets = aggregatedSubjects.getBuckets();
@ -295,7 +336,6 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return codes;
}
// @VisibleForTesting
public SearchRequest buildObservationAllFieldsCompositeSearchRequest(int maxResultSetSize, SearchParameterMap theSearchParameterMap, int theMaxObservationsPerCode) {
return buildObservationsSearchRequest(theSearchParameterMap, createCompositeAggregationBuilder(maxResultSetSize, theMaxObservationsPerCode, null));
@ -307,7 +347,6 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return buildObservationsSearchRequest(theSearchParameterMap, createCompositeAggregationBuilder(maxResultSetSize, theMaxObservationsPerCode, topHitsInclude));
}
// @VisibleForTesting
public SearchRequest buildObservationAllFieldsTermsSearchRequest(int maxResultSetSize, SearchParameterMap theSearchParameterMap, int theMaxObservationsPerCode) {
return buildObservationsSearchRequest(theSearchParameterMap, createTermsAggregationBuilder(maxResultSetSize, theMaxObservationsPerCode, null));
}
@ -518,18 +557,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
}
// @VisibleForTesting
public boolean deleteIndex(String theIndexName) throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest(theIndexName);
AcknowledgedResponse deleteIndexResponse = myRestHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
return deleteIndexResponse.isAcknowledged();
}
// @VisibleForTesting
public void deleteAllDocuments(String theIndexName) throws IOException {
void deleteAllDocuments(String theIndexName) throws IOException {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(theIndexName);
deleteByQueryRequest.setQuery(QueryBuilders.matchAllQuery());
myRestHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);

@ -2,6 +2,13 @@ package ca.uhn.fhir.jpa.search.lastn;
//import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import java.util.List;
public interface IElasticsearchSvc {
@ -9,4 +16,5 @@ public interface IElasticsearchSvc {
// IBundleProvider uniqueCodes(javax.servlet.http.HttpServletRequest theServletRequest, ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails);
List<ResourcePersistentId> executeLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, IdHelperService theIdHelperService);
}

@ -1,26 +0,0 @@
package ca.uhn.fhir.jpa.search.lastn.config;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.io.IOException;
@Configuration
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory")
@EnableTransactionManagement
public class ElasticsearchConfig {
private final String elasticsearchHost = "127.0.0.1";
private final Integer elasticsearchPort = 9301;
private final String elasticsearchUserId = "elastic";
private final String elasticsearchPassword = "changeme";
@Bean()
public ElasticsearchSvcImpl myElasticsearchSvc() throws IOException {
return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUserId, elasticsearchPassword);
}
}

@ -53,16 +53,24 @@ public class ObservationJson {
private String myCode_concept_text;
@JsonProperty(value = "codeconceptcodingcode", required = false)
private List<String> myCode_coding_code = new ArrayList<>();
// TODO: Temporary change until sort out how to deal with multiple observation code codings
// private List<String> myCode_coding_code = new ArrayList<>();
private String myCode_coding_code;
@JsonProperty(value = "codeconceptcodingcode_system_hash", required = false)
private List<String> myCode_coding_code_system_hash = new ArrayList<>();
// TODO: Temporary change until sort out how to deal with multiple observation code codings
// private List<String> myCode_coding_code_system_hash = new ArrayList<>();
private String myCode_coding_code_system_hash;
@JsonProperty(value = "codeconceptcodingdisplay", required = false)
private List<String> myCode_coding_display = new ArrayList<>();
// TODO: Temporary change until sort out how to deal with multiple observation code codings
// private List<String> myCode_coding_display = new ArrayList<>();
private String myCode_coding_display;
@JsonProperty(value = "codeconceptcodingsystem", required = false)
private List<String> myCode_coding_system = new ArrayList<>();
// TODO: Temporary change until sort out how to deal with multiple observation code codings
// private List<String> myCode_coding_system = new ArrayList<>();
private String myCode_coding_system;
@JsonProperty(value = "effectivedtm", required = true)
private Date myEffectiveDtm;
@ -120,10 +128,16 @@ public class ObservationJson {
public void setCode(CodeableConcept theCode) {
myCode_concept_text = theCode.getText();
for(Coding theCodeCoding : theCode.getCoding()) {
myCode_coding_code_system_hash.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCodeCoding.getSystem(), theCodeCoding.getCode())));
// TODO: Temporary change until address how to manage multiple Observation Code codings.
/* myCode_coding_code_system_hash.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCodeCoding.getSystem(), theCodeCoding.getCode())));
myCode_coding_code.add(theCodeCoding.getCode());
myCode_coding_display.add(theCodeCoding.getDisplay());
myCode_coding_system.add(theCodeCoding.getSystem());
*/
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();
}
}
@ -132,6 +146,8 @@ public class ObservationJson {
return myCode_concept_text;
}
// TODO: Temporary changes until resolve problem of how to manage Observation Code with multiple codings
/*
public List<String> getCode_coding_code_system_hash() {
return myCode_coding_code_system_hash;
}
@ -147,6 +163,22 @@ public class ObservationJson {
public List<String> getCode_coding_system() {
return myCode_coding_system;
}
*/
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;

@ -1,13 +1,19 @@
package ca.uhn.fhir.jpa.dao.lastn;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.lastn.config.TestIntegratedObservationIndexSearchConfig;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodeableConceptSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedSearchParamLastNDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.shadehapi.elasticsearch.action.search.SearchRequest;
import org.shadehapi.elasticsearch.action.search.SearchResponse;
import org.hl7.fhir.r4.model.*;
@ -16,11 +22,14 @@ 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 java.io.IOException;
import java.io.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
@ -35,11 +44,14 @@ public class IntegratedObservationIndexedSearchParamLastNTest {
IObservationIndexedCodeCodeableConceptSearchParamDao myCodeableConceptIndexedSearchParamNormalizedDao;
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
ObservationLastNIndexPersistR4Svc myObservationLastNIndexPersistR4Svc;
@Autowired
private ElasticsearchSvcImpl elasticsearchSvc;
@Autowired
private IFhirSystemDao<Bundle, Meta> myDao;
final String RESOURCEPID = "123";
final String SUBJECTID = "4567";
final String CATEGORYFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-category";
@ -107,11 +119,11 @@ public class IntegratedObservationIndexedSearchParamLastNTest {
String observationCodeText = "Test Codeable Concept Field for Code";
CodeableConcept codeableConceptField = new CodeableConcept().setText(observationCodeText);
codeableConceptField.addCoding(new Coding("http://mycodes.org/fhir/observation-code", "test-code", "test-code display"));
codeableConceptField.addCoding(new Coding("http://myalternatecodes.org/fhir/observation-code", "test-alt-code", "test-alt-code display"));
codeableConceptField.addCoding(new Coding("http://mysecondaltcodes.org/fhir/observation-code", "test-second-alt-code", "test-second-alt-code display"));
// codeableConceptField.addCoding(new Coding("http://myalternatecodes.org/fhir/observation-code", "test-alt-code", "test-alt-code display"));
// codeableConceptField.addCoding(new Coding("http://mysecondaltcodes.org/fhir/observation-code", "test-second-alt-code", "test-second-alt-code display"));
myObservation.setCode(codeableConceptField);
myObservationLastNIndexPersistSvc.indexObservation(myObservation);
myObservationLastNIndexPersistR4Svc.indexObservation(myObservation);
SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", SUBJECTID);
@ -131,18 +143,18 @@ public class IntegratedObservationIndexedSearchParamLastNTest {
assertEquals(RESOURCEPID, observationIdOnly.getIdentifier());
// execute Observation ID search - Composite Aggregation
// searchRequestIdsOnly = elasticsearchSvc.buildObservationAllFieldsCompositeSearchRequest(1000, searchParameterMap, 3);
// responseIds = elasticsearchSvc.executeSearchRequest(searchRequestIdsOnly);
// observationIdsOnly = elasticsearchSvc.buildObservationCompositeResults(responseIds);
searchRequestIdsOnly = elasticsearchSvc.buildObservationAllFieldsCompositeSearchRequest(1000, searchParameterMap, 3);
responseIds = elasticsearchSvc.executeSearchRequest(searchRequestIdsOnly);
observationIdsOnly = elasticsearchSvc.buildObservationCompositeResults(responseIds);
// assertEquals(1, observationIdsOnly.size());
// observationIdOnly = observationIdsOnly.get(0);
// assertEquals(RESOURCEPID, observationIdOnly.getIdentifier());
assertEquals(1, observationIdsOnly.size());
observationIdOnly = observationIdsOnly.get(0);
assertEquals(RESOURCEPID, observationIdOnly.getIdentifier());
}
// @Test
@Test
public void testIndexObservationMultiple() {
// Create two CodeableConcept values each for a Code with three codings.
@ -200,7 +212,7 @@ public class IntegratedObservationIndexedSearchParamLastNTest {
Date effectiveDtm = observationDate.getTime();
observation.setEffective(new DateTimeType(effectiveDtm));
myObservationLastNIndexPersistSvc.indexObservation(observation);
myObservationLastNIndexPersistR4Svc.indexObservation(observation);
}
@ -211,4 +223,57 @@ public class IntegratedObservationIndexedSearchParamLastNTest {
}
@Test
public void testSampleBundle() {
FhirContext myFhirCtx = FhirContext.forR4();
PathMatchingResourcePatternResolver provider = new PathMatchingResourcePatternResolver();
final Resource[] bundleResources;
try {
bundleResources = provider.getResources("lastntestbundle.json");
} catch (IOException e) {
throw new RuntimeException("Unexpected error during transmission: " + e.toString(), e);
}
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
}
}
/*
* SMART demo apps rely on the use of LOINC 3141-9 (Body Weight Measured)
* instead of LOINC 29463-7 (Body Weight)
*/
nextBundleString = nextBundleString.replace("\"29463-7\"", "\"3141-9\"");
IParser parser = myFhirCtx.newJsonParser();
parser.setParserErrorHandler(new LenientErrorHandler(false));
Bundle bundle = parser.parseResource(Bundle.class, nextBundleString);
myDao.transaction(null, bundle);
}
);
}
}

@ -29,7 +29,7 @@ public class PersistObservationIndexedSearchParamLastNTest {
IObservationIndexedCodeCodeableConceptSearchParamDao myCodeableConceptIndexedSearchParamNormalizedDao;
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
ObservationLastNIndexPersistR4Svc myObservationLastNIndexPersistR4Svc;
@Before
public void before() {
@ -94,7 +94,7 @@ public class PersistObservationIndexedSearchParamLastNTest {
codeableConceptField.addCoding(new Coding("http://mysecondaltcodes.org/fhir/observation-code", "test-second-alt-code", "test-second-alt-code display"));
myObservation.setCode(codeableConceptField);
myObservationLastNIndexPersistSvc.indexObservation(myObservation);
myObservationLastNIndexPersistR4Svc.indexObservation(myObservation);
List<ObservationIndexedSearchParamLastNEntity> persistedObservationEntities = myResourceIndexedObservationLastNDao.findAll();
assertEquals(1, persistedObservationEntities.size());
@ -171,7 +171,7 @@ public class PersistObservationIndexedSearchParamLastNTest {
Date effectiveDtm = observationDate.getTime();
observation.setEffective(new DateTimeType(effectiveDtm));
myObservationLastNIndexPersistSvc.indexObservation(observation);
myObservationLastNIndexPersistR4Svc.indexObservation(observation);
}
@ -180,6 +180,11 @@ public class PersistObservationIndexedSearchParamLastNTest {
assertEquals(100, myResourceIndexedObservationLastNDao.count());
assertEquals(2, myCodeableConceptIndexedSearchParamNormalizedDao.count());
}
}
@Test
public void testSampleObservationResource() {
}
}

@ -10,7 +10,7 @@ import java.io.IOException;
@Configuration
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
basePackages = {"ca.uhn.fhir.jpa.dao"})
basePackages = {"ca.uhn.fhir.jpa.dao.data"})
@EnableTransactionManagement
public class TestIntegratedObservationIndexSearchConfig extends TestObservationIndexSearchConfig {

@ -15,6 +15,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import pl.allegro.tech.embeddedelasticsearch.EmbeddedElastic;
import pl.allegro.tech.embeddedelasticsearch.PopularProperties;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Properties;
import java.util.UUID;
@ -22,7 +23,7 @@ import java.util.concurrent.TimeUnit;
@Configuration
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
basePackages = {"ca.uhn.fhir.jpa.dao"})
basePackages = {"ca.uhn.fhir.jpa.dao.data"})
@EnableTransactionManagement
public class TestObservationIndexSearchConfig extends TestR4Config {
@ -84,5 +85,9 @@ public class TestObservationIndexSearchConfig extends TestR4Config {
return embeddedElastic;
}
@PreDestroy
public void stop() {
embeddedElasticSearch().stop();
}
}

@ -35,7 +35,7 @@ import static org.junit.Assert.assertEquals;
@Ignore
public class ElasticsearchV5PerformanceTests {
/*
private ElasticsearchV5SvcImpl elasticsearchSvc = new ElasticsearchV5SvcImpl("localhost", 9301, "elastic", "changeme");
private List<String> patientIds = new ArrayList<>();
@ -148,6 +148,6 @@ public class ElasticsearchV5PerformanceTests {
}
return identifiers;
}
*/
}

@ -234,6 +234,8 @@ public class LastNElasticsearchSvcSingleObservationTest {
String code_concept_text_values = observation.getCode_concept_text();
assertEquals(OBSERVATIONCODETEXT, code_concept_text_values);
// TODO: Temporary changes until find a solution for addressing Observation Code with multiple codings.
/*
List<String> code_coding_systems = observation.getCode_coding_system();
assertEquals(3,code_coding_systems.size());
assertEquals(CODEFIRSTCODINGSYSTEM, code_coding_systems.get(0));
@ -257,6 +259,18 @@ public class LastNElasticsearchSvcSingleObservationTest {
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE)), code_coding_code_system_hash.get(0));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODESECONDCODINGSYSTEM, CODESECONDCODINGCODE)), code_coding_code_system_hash.get(1));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODETHIRDCODINGSYSTEM, CODETHIRDCODINGCODE)), code_coding_code_system_hash.get(2));
*/
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
SearchRequest searchRequest = elasticsearchSvc.buildObservationCodesSearchRequest(1000);

@ -34,7 +34,7 @@ import static org.junit.Assert.*;
public class LastNElasticsearchV5SvcMultipleObservationsTest {
@Autowired
private ElasticsearchV5SvcImpl elasticsearchSvc;
// private ElasticsearchV5SvcImpl elasticsearchSvc;
private static ObjectMapper ourMapperNonPrettyPrint;
@ -56,8 +56,8 @@ public class LastNElasticsearchV5SvcMultipleObservationsTest {
@After
public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(IndexConstants.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(IndexConstants.CODE_INDEX);
// elasticsearchSvc.deleteAllDocuments(IndexConstants.OBSERVATION_INDEX);
// elasticsearchSvc.deleteAllDocuments(IndexConstants.CODE_INDEX);
}
/*
@Test

@ -37,7 +37,7 @@ import static org.junit.Assert.assertTrue;
public class LastNElasticsearchV5SvcSingleObservationTest {
@Autowired
ElasticsearchV5SvcImpl elasticsearchSvc;
// ElasticsearchV5SvcImpl elasticsearchSvc;
static ObjectMapper ourMapperNonPrettyPrint;
@ -93,14 +93,14 @@ public class LastNElasticsearchV5SvcSingleObservationTest {
// @Before
public void before() throws IOException {
elasticsearchSvc.deleteAllDocuments(IndexConstants.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(IndexConstants.CODE_INDEX);
// elasticsearchSvc.deleteAllDocuments(IndexConstants.OBSERVATION_INDEX);
// elasticsearchSvc.deleteAllDocuments(IndexConstants.CODE_INDEX);
}
@After
public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(IndexConstants.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(IndexConstants.CODE_INDEX);
// elasticsearchSvc.deleteAllDocuments(IndexConstants.OBSERVATION_INDEX);
// elasticsearchSvc.deleteAllDocuments(IndexConstants.CODE_INDEX);
}
@Test
public void testSingleObservationQuery() throws IOException {
@ -222,7 +222,7 @@ public class LastNElasticsearchV5SvcSingleObservationTest {
String code_concept_text_values = observation.getCode_concept_text();
assertEquals(OBSERVATIONCODETEXT, code_concept_text_values);
/*
List<String> code_coding_systems = observation.getCode_coding_system();
assertEquals(3,code_coding_systems.size());
assertEquals(CODEFIRSTCODINGSYSTEM, code_coding_systems.get(0));
@ -246,7 +246,7 @@ public class LastNElasticsearchV5SvcSingleObservationTest {
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE)), code_coding_code_system_hash.get(0));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODESECONDCODINGSYSTEM, CODESECONDCODINGCODE)), code_coding_code_system_hash.get(1));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODETHIRDCODINGSYSTEM, CODETHIRDCODINGCODE)), code_coding_code_system_hash.get(2));
*/
// Retrieve all Observation codes
/* SearchRequest searchRequest = elasticsearchSvc.buildObservationCodesSearchRequest(1000);
SearchResponse response = elasticsearchSvc.executeSearchRequest(searchRequest);
@ -329,11 +329,11 @@ public class LastNElasticsearchV5SvcSingleObservationTest {
indexedObservation.setCode(codeableConceptField);
String observationDocument = ourMapperNonPrettyPrint.writeValueAsString(indexedObservation);
assertTrue(elasticsearchSvc.performIndex(IndexConstants.OBSERVATION_INDEX, RESOURCEPID, observationDocument, IndexConstants.OBSERVATION_DOCUMENT_TYPE));
// assertTrue(elasticsearchSvc.performIndex(IndexConstants.OBSERVATION_INDEX, RESOURCEPID, observationDocument, IndexConstants.OBSERVATION_DOCUMENT_TYPE));
CodeJson observationCode = new CodeJson(codeableConceptField, OBSERVATIONSINGLECODEID);
String codeDocument = ourMapperNonPrettyPrint.writeValueAsString(observationCode);
assertTrue(elasticsearchSvc.performIndex(IndexConstants.CODE_INDEX, OBSERVATIONSINGLECODEID, codeDocument, IndexConstants.CODE_DOCUMENT_TYPE));
// assertTrue(elasticsearchSvc.performIndex(IndexConstants.CODE_INDEX, OBSERVATIONSINGLECODEID, codeDocument, IndexConstants.CODE_DOCUMENT_TYPE));
try {
Thread.sleep(1000L);

@ -9,6 +9,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
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;
@ -50,4 +51,9 @@ public class TestElasticsearchConfig {
return embeddedElastic;
}
@PreDestroy
public void stop() {
embeddedElasticSearch().stop();
}
}

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.search.lastn.config;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchV5SvcImpl;
//import ca.uhn.fhir.jpa.search.lastn.ElasticsearchV5SvcImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@ -13,9 +13,9 @@ import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory")
@EnableTransactionManagement
//@Configuration
//@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory")
//@EnableTransactionManagement
public class TestElasticsearchV5Config {
private final String elasticsearchHost = "127.0.0.1";
@ -24,7 +24,7 @@ public class TestElasticsearchV5Config {
private static final String ELASTIC_VERSION = "5.6.16";
/*
@Bean()
public ElasticsearchV5SvcImpl myElasticsearchSvc() throws IOException {
int elasticsearchPort = embeddedElasticSearch().getHttpPort();
@ -49,5 +49,5 @@ public class TestElasticsearchV5Config {
return embeddedElastic;
}
*/
}

@ -191,6 +191,11 @@ public class JpaConstants {
*/
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

@ -61,6 +61,7 @@ public class SearchParameterMap implements Serializable {
private SummaryEnum mySummaryMode;
private SearchTotalModeEnum mySearchTotalMode;
private QuantityParam myNearDistanceParam;
private boolean myLastN;
/**
* Constructor
@ -303,6 +304,24 @@ 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;
}
/**
* This method creates a URL query string representation of the parameters in this
* object, excluding the part before the parameters, e.g.

@ -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' || ${className} == 'Observation' ))
BaseJpaResourceProvider${className}${versionCapitalized}
#else
JpaResourceProvider${versionCapitalized}<${className}>

@ -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

@ -68,6 +68,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
@ -82,6 +84,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}>();