Implement CDS on FHIR in CDS Hooks (#5240)
* WIP * WIP refactor * Initial commit * measureRefactor WIP * version bump * Bump to core release 6.0.22 (#5028) * Bump to core release 6.0.16 * Bump to core version 6.0.20 * Fix errors thrown as a result of VersionSpecificWorkerContextWrapper * Bump to core 6.0.22 * adding tests WIP * Rework MeasureServiceFActory * measure refactor WIP * Resolve 5126 hfj res ver prov might cause migration error on db that automatically indexes the primary key (#5127) * dropped old index FK_RESVERPROV_RES_PID on RES_PID column before adding IDX_RESVERPROV_RES_PID * added changelog * changed to valid version number * changed to valid version number, need to be ordered by version number... * generic provider loader * provider loader update * tests wip * 5123 - Use DEFAULT partition for server-based requests if none specified (#5124) 5123 - Use DEFAULT partition for server-based requests if none specified * consent remove all suppresses next link in bundle (#5119) * added FIXME with source of issue * added FIXME with root cause * added FIXME with root cause * Providing solution to the issue and removing fixmes. * Providing changelog * auto-formatting. * Adding new test. * Adding a new test for standard paging * let's try this and see if it works...? * fix tests * cleanup to trigger a new run * fixing tests --------- Co-authored-by: Ken Stevens <ken@smilecdr.com> Co-authored-by: peartree <etienne.poirier@smilecdr.com> * 5117 MDM Score for No Match Fields Should Not Be Included in Total Score (#5118) * fix, test, changelog * fix, test, changelog --------- Co-authored-by: justindar <justin.dar@smilecdr.com> * measureRefactor tests WIP * update tests wip * remove baseCrR4Test * update imports * add paging provider config to tests * dstu3 tests * _source search parameter needs to support modifiers (#5095) _source search parameter needs to support modifiers - added support form :contains, :missing, :above modifiers * latest work * cleanup * Refactor operation providers to support use in multiple modules * Fix HFQL docs (#5151) * repository measure refactor * undo bundle edit, add config * embedded library true * undo post of bundle * clear library cache on test * fix sumbit-data test * Fix tests * cleanup * Update MeasureService.java * latest * 3.0.0 engine update * merge cleanup * cr updates * cleanup * Only create services for PlanDefs that have a trigger with a named event * Fix parameters being sent through invoke * Handle system actions * cleanup * missing packages from cql * fix test data and test cases for repository api * latest * fix pom * fix test config * cleanup * 3.0.0 clinical reasoning uplift * r4 cql execution provider and tests * fix submitdata provider config * wip cql tests * debugging cql op wip * wip debugging cql * update tests for $cql, fix class names * prep for Pre6 uplift * spotless checks and test fixes * bump to 3.0.0-PRE6 * cleanup * fix version * cleanup * cleanup * fix exlusions in pom * add in cache invalidation config * fix resource resolution * cleanup * search converter bug and test for repository * update pom * update searchconverter * version bump, add changelog * remove term config, move IDaoRegistryUser class * Break out dstu3 providers and move config to version folders * merge cleanup * merge cleanup * Add changelog * spotless * fix error codes * cleanup * cleanup * Handle missing beans in Operation configs * spotless * Use CondtionalOnBean for CR Operation and Repository configs * Fix test config * Add RepositoryFactory and RestfulServer to CdsConfigService * Move createRequestDetails into CdsConfigService * spotless * review comments --------- Co-authored-by: justin.mckelvy <justin.mckelvy@smilecdr.com> Co-authored-by: tadgh <garygrantgraham@gmail.com> Co-authored-by: dotasek <david.otasek@smilecdr.com> Co-authored-by: Jonathan Percival <jonathan.i.percival@gmail.com> Co-authored-by: TynerGjs <132295567+TynerGjs@users.noreply.github.com> Co-authored-by: Steve Corbett <137920358+steve-corbett-smilecdr@users.noreply.github.com> Co-authored-by: Ken Stevens <khstevens@gmail.com> Co-authored-by: Ken Stevens <ken@smilecdr.com> Co-authored-by: peartree <etienne.poirier@smilecdr.com> Co-authored-by: jdar8 <69840459+jdar8@users.noreply.github.com> Co-authored-by: justindar <justin.dar@smilecdr.com> Co-authored-by: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com> Co-authored-by: Nathan Doef <n.doef@protonmail.com> Co-authored-by: Justin McKelvy <60718638+Capt-Mac@users.noreply.github.com>
This commit is contained in:
parent
6ee6031a5f
commit
a2204654df
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 5238
|
||||||
|
title: "Added an implementation of Clinical Reasoning CDS on FHIR to the CDS Hooks module that allows PlanDefinition
|
||||||
|
worfklows to be processed as CDS Services using the $apply operation.
|
||||||
|
"
|
|
@ -33,6 +33,11 @@
|
||||||
<artifactId>hapi-fhir-storage</artifactId>
|
<artifactId>hapi-fhir-storage</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
|
<artifactId>hapi-fhir-storage-cr</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
|
|
|
@ -20,8 +20,14 @@
|
||||||
package ca.uhn.hapi.fhir.cdshooks.api;
|
package ca.uhn.hapi.fhir.cdshooks.api;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.cr.common.IRepositoryFactory;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.SystemRestfulResponse;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.opencds.cqf.fhir.utility.Ids;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -37,4 +43,22 @@ public interface ICdsConfigService {
|
||||||
default DaoRegistry getDaoRegistry() {
|
default DaoRegistry getDaoRegistry() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
default IRepositoryFactory getRepositoryFactory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
default RestfulServer getRestfulServer() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default RequestDetails createRequestDetails(FhirContext theFhirContext, String theId, String theResourceType) {
|
||||||
|
SystemRequestDetails rd = new SystemRequestDetails();
|
||||||
|
rd.setServer(getRestfulServer());
|
||||||
|
rd.setResponse(new SystemRestfulResponse(rd));
|
||||||
|
rd.setId(Ids.newId(theFhirContext.getVersion().getVersion(), theResourceType, theId));
|
||||||
|
return rd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,14 @@ public interface ICdsServiceRegistry {
|
||||||
boolean theAllowAutoFhirClientPrefetch,
|
boolean theAllowAutoFhirClientPrefetch,
|
||||||
String theModuleId);
|
String theModuleId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new Clinical Reasoning CDS Service with the endpoint.
|
||||||
|
*
|
||||||
|
* @param theServiceId the id of the service PlanDefinition
|
||||||
|
* @return the service was registered
|
||||||
|
*/
|
||||||
|
boolean registerCrService(String theServiceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove registered CDS service with the service ID, only removes dynamically registered service
|
* Remove registered CDS service with the service ID, only removes dynamically registered service
|
||||||
*
|
*
|
||||||
|
|
|
@ -20,8 +20,13 @@
|
||||||
package ca.uhn.hapi.fhir.cdshooks.config;
|
package ca.uhn.hapi.fhir.cdshooks.config;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.cr.common.IRepositoryFactory;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry;
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsConfigService;
|
import ca.uhn.hapi.fhir.cdshooks.api.ICdsConfigService;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsHooksDaoAuthorizationSvc;
|
import ca.uhn.hapi.fhir.cdshooks.api.ICdsHooksDaoAuthorizationSvc;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceRegistry;
|
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceRegistry;
|
||||||
|
@ -29,11 +34,21 @@ import ca.uhn.hapi.fhir.cdshooks.module.CdsHooksObjectMapperFactory;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.svc.CdsConfigServiceImpl;
|
import ca.uhn.hapi.fhir.cdshooks.svc.CdsConfigServiceImpl;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.svc.CdsHooksContextBooter;
|
import ca.uhn.hapi.fhir.cdshooks.svc.CdsHooksContextBooter;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.svc.CdsServiceRegistryImpl;
|
import ca.uhn.hapi.fhir.cdshooks.svc.CdsServiceRegistryImpl;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrServiceDstu3;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrServiceR4;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrServiceR5;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsServiceInterceptor;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.ICdsCrServiceFactory;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery.CrDiscoveryServiceDstu3;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery.CrDiscoveryServiceR4;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery.CrDiscoveryServiceR5;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery.ICrDiscoveryServiceFactory;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchDaoSvc;
|
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchDaoSvc;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchFhirClientSvc;
|
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchFhirClientSvc;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchSvc;
|
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchSvc;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsResolutionStrategySvc;
|
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsResolutionStrategySvc;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -44,12 +59,23 @@ public class CdsHooksConfig {
|
||||||
|
|
||||||
public static final String CDS_HOOKS_OBJECT_MAPPER_FACTORY = "cdsHooksObjectMapperFactory";
|
public static final String CDS_HOOKS_OBJECT_MAPPER_FACTORY = "cdsHooksObjectMapperFactory";
|
||||||
|
|
||||||
|
public static final String PLAN_DEFINITION_RESOURCE_NAME = "PlanDefinition";
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private DaoRegistry myDaoRegistry;
|
private DaoRegistry myDaoRegistry;
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private MatchUrlService myMatchUrlService;
|
private MatchUrlService myMatchUrlService;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private IResourceChangeListenerRegistry myResourceChangeListenerRegistry;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private IRepositoryFactory myRepositoryFactory;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private RestfulServer myRestfulServer;
|
||||||
|
|
||||||
@Bean(name = CDS_HOOKS_OBJECT_MAPPER_FACTORY)
|
@Bean(name = CDS_HOOKS_OBJECT_MAPPER_FACTORY)
|
||||||
public ObjectMapper objectMapper(FhirContext theFhirContext) {
|
public ObjectMapper objectMapper(FhirContext theFhirContext) {
|
||||||
return new CdsHooksObjectMapperFactory(theFhirContext).newMapper();
|
return new CdsHooksObjectMapperFactory(theFhirContext).newMapper();
|
||||||
|
@ -59,14 +85,78 @@ public class CdsHooksConfig {
|
||||||
public ICdsServiceRegistry cdsServiceRegistry(
|
public ICdsServiceRegistry cdsServiceRegistry(
|
||||||
CdsHooksContextBooter theCdsHooksContextBooter,
|
CdsHooksContextBooter theCdsHooksContextBooter,
|
||||||
CdsPrefetchSvc theCdsPrefetchSvc,
|
CdsPrefetchSvc theCdsPrefetchSvc,
|
||||||
@Qualifier(CDS_HOOKS_OBJECT_MAPPER_FACTORY) ObjectMapper theObjectMapper) {
|
@Qualifier(CDS_HOOKS_OBJECT_MAPPER_FACTORY) ObjectMapper theObjectMapper,
|
||||||
return new CdsServiceRegistryImpl(theCdsHooksContextBooter, theCdsPrefetchSvc, theObjectMapper);
|
ICdsCrServiceFactory theCdsCrServiceFactory,
|
||||||
|
ICrDiscoveryServiceFactory theCrDiscoveryServiceFactory) {
|
||||||
|
return new CdsServiceRegistryImpl(
|
||||||
|
theCdsHooksContextBooter,
|
||||||
|
theCdsPrefetchSvc,
|
||||||
|
theObjectMapper,
|
||||||
|
theCdsCrServiceFactory,
|
||||||
|
theCrDiscoveryServiceFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ICdsCrServiceFactory cdsCrServiceFactory(FhirContext theFhirContext, ICdsConfigService theCdsConfigService) {
|
||||||
|
return id -> {
|
||||||
|
if (myRepositoryFactory == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
RequestDetails rd =
|
||||||
|
theCdsConfigService.createRequestDetails(theFhirContext, id, PLAN_DEFINITION_RESOURCE_NAME);
|
||||||
|
Repository repository = myRepositoryFactory.create(rd);
|
||||||
|
switch (theFhirContext.getVersion().getVersion()) {
|
||||||
|
case DSTU3:
|
||||||
|
return new CdsCrServiceDstu3(rd, repository);
|
||||||
|
case R4:
|
||||||
|
return new CdsCrServiceR4(rd, repository);
|
||||||
|
case R5:
|
||||||
|
return new CdsCrServiceR5(rd, repository);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ICrDiscoveryServiceFactory crDiscoveryServiceFactory(
|
||||||
|
FhirContext theFhirContext, ICdsConfigService theCdsConfigService) {
|
||||||
|
return id -> {
|
||||||
|
if (myRepositoryFactory == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
RequestDetails rd =
|
||||||
|
theCdsConfigService.createRequestDetails(theFhirContext, id, PLAN_DEFINITION_RESOURCE_NAME);
|
||||||
|
Repository repository = myRepositoryFactory.create(rd);
|
||||||
|
switch (theFhirContext.getVersion().getVersion()) {
|
||||||
|
case DSTU3:
|
||||||
|
return new CrDiscoveryServiceDstu3(rd.getId(), repository);
|
||||||
|
case R4:
|
||||||
|
return new CrDiscoveryServiceR4(rd.getId(), repository);
|
||||||
|
case R5:
|
||||||
|
return new CrDiscoveryServiceR5(rd.getId(), repository);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CdsServiceInterceptor cdsServiceInterceptor() {
|
||||||
|
if (myResourceChangeListenerRegistry == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CdsServiceInterceptor listener = new CdsServiceInterceptor();
|
||||||
|
myResourceChangeListenerRegistry.registerResourceResourceChangeListener(
|
||||||
|
PLAN_DEFINITION_RESOURCE_NAME, SearchParameterMap.newSynchronous(), listener, 1000);
|
||||||
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ICdsConfigService cdsConfigService(
|
public ICdsConfigService cdsConfigService(
|
||||||
FhirContext theFhirContext, @Qualifier(CDS_HOOKS_OBJECT_MAPPER_FACTORY) ObjectMapper theObjectMapper) {
|
FhirContext theFhirContext, @Qualifier(CDS_HOOKS_OBJECT_MAPPER_FACTORY) ObjectMapper theObjectMapper) {
|
||||||
return new CdsConfigServiceImpl(theFhirContext, theObjectMapper, myDaoRegistry);
|
return new CdsConfigServiceImpl(
|
||||||
|
theFhirContext, theObjectMapper, myDaoRegistry, myRepositoryFactory, myRestfulServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.model.api.IModelJson;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.ICdsMethod;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.ICdsCrServiceFactory;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
abstract class BaseCdsCrMethod implements ICdsMethod {
|
||||||
|
private ICdsCrServiceFactory myCdsCrServiceFactory;
|
||||||
|
|
||||||
|
public BaseCdsCrMethod(ICdsCrServiceFactory theCdsCrServiceFactory) {
|
||||||
|
myCdsCrServiceFactory = theCdsCrServiceFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object invoke(ObjectMapper theObjectMapper, IModelJson theJson, String theServiceId) {
|
||||||
|
try {
|
||||||
|
return myCdsCrServiceFactory.create(theServiceId).invoke(theJson);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e.getCause() != null && e.getCause() instanceof BaseServerResponseException) {
|
||||||
|
throw (BaseServerResponseException) e.getCause();
|
||||||
|
}
|
||||||
|
throw new ConfigurationException(Msg.code(2434) + "Failed to invoke $apply on " + theServiceId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,9 @@
|
||||||
package ca.uhn.hapi.fhir.cdshooks.svc;
|
package ca.uhn.hapi.fhir.cdshooks.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.cr.common.IRepositoryFactory;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsConfigService;
|
import ca.uhn.hapi.fhir.cdshooks.api.ICdsConfigService;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@ -31,14 +33,20 @@ public class CdsConfigServiceImpl implements ICdsConfigService {
|
||||||
private final FhirContext myFhirContext;
|
private final FhirContext myFhirContext;
|
||||||
private final ObjectMapper myObjectMapper;
|
private final ObjectMapper myObjectMapper;
|
||||||
private final DaoRegistry myDaoRegistry;
|
private final DaoRegistry myDaoRegistry;
|
||||||
|
private final IRepositoryFactory myRepositoryFactory;
|
||||||
|
private final RestfulServer myRestfulServer;
|
||||||
|
|
||||||
public CdsConfigServiceImpl(
|
public CdsConfigServiceImpl(
|
||||||
@Nonnull FhirContext theFhirContext,
|
@Nonnull FhirContext theFhirContext,
|
||||||
@Nonnull ObjectMapper theObjectMapper,
|
@Nonnull ObjectMapper theObjectMapper,
|
||||||
@Nullable DaoRegistry theDaoRegistry) {
|
@Nullable DaoRegistry theDaoRegistry,
|
||||||
|
@Nullable IRepositoryFactory theRepositoryFactory,
|
||||||
|
@Nullable RestfulServer theRestfulServer) {
|
||||||
myFhirContext = theFhirContext;
|
myFhirContext = theFhirContext;
|
||||||
myObjectMapper = theObjectMapper;
|
myObjectMapper = theObjectMapper;
|
||||||
myDaoRegistry = theDaoRegistry;
|
myDaoRegistry = theDaoRegistry;
|
||||||
|
myRepositoryFactory = theRepositoryFactory;
|
||||||
|
myRestfulServer = theRestfulServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -58,4 +66,16 @@ public class CdsConfigServiceImpl implements ICdsConfigService {
|
||||||
public DaoRegistry getDaoRegistry() {
|
public DaoRegistry getDaoRegistry() {
|
||||||
return myDaoRegistry;
|
return myDaoRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IRepositoryFactory getRepositoryFactory() {
|
||||||
|
return myRepositoryFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public RestfulServer getRestfulServer() {
|
||||||
|
return myRestfulServer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc;
|
||||||
|
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceMethod;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.ICdsCrServiceFactory;
|
||||||
|
|
||||||
|
public class CdsCrServiceMethod extends BaseCdsCrMethod implements ICdsServiceMethod {
|
||||||
|
private final CdsServiceJson myCdsServiceJson;
|
||||||
|
|
||||||
|
public CdsCrServiceMethod(CdsServiceJson theCdsServiceJson, ICdsCrServiceFactory theCdsCrServiceFactory) {
|
||||||
|
super(theCdsCrServiceFactory);
|
||||||
|
myCdsServiceJson = theCdsServiceJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CdsServiceJson getCdsServiceJson() {
|
||||||
|
return myCdsServiceJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowAutoFhirClientPrefetch() {
|
||||||
|
// The $apply operation will make FHIR requests for any data it needs
|
||||||
|
// directly against the fhirServer of the ServiceRequest.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,8 @@ import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServicesJson;
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServicesJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.ICdsCrServiceFactory;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery.ICrDiscoveryServiceFactory;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -33,6 +35,8 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_CR_MODULE_ID;
|
||||||
|
|
||||||
public class CdsServiceCache {
|
public class CdsServiceCache {
|
||||||
static final Logger ourLog = LoggerFactory.getLogger(CdsServiceCache.class);
|
static final Logger ourLog = LoggerFactory.getLogger(CdsServiceCache.class);
|
||||||
final Map<String, ICdsMethod> myServiceMap = new LinkedHashMap<>();
|
final Map<String, ICdsMethod> myServiceMap = new LinkedHashMap<>();
|
||||||
|
@ -66,6 +70,23 @@ public class CdsServiceCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void registerCrService(
|
||||||
|
String theServiceId,
|
||||||
|
ICrDiscoveryServiceFactory theDiscoveryServiceFactory,
|
||||||
|
ICdsCrServiceFactory theCrServiceFactory) {
|
||||||
|
if (!isCdsServiceAlreadyRegistered(theServiceId, CDS_CR_MODULE_ID)) {
|
||||||
|
CdsServiceJson cdsServiceJson =
|
||||||
|
theDiscoveryServiceFactory.create(theServiceId).resolveService();
|
||||||
|
if (cdsServiceJson != null) {
|
||||||
|
final CdsCrServiceMethod cdsCrServiceMethod =
|
||||||
|
new CdsCrServiceMethod(cdsServiceJson, theCrServiceFactory);
|
||||||
|
myServiceMap.put(theServiceId, cdsCrServiceMethod);
|
||||||
|
myCdsServiceJson.addService(cdsServiceJson);
|
||||||
|
ourLog.info("Created service for {}", theServiceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void registerFeedback(String theServiceId, Object theServiceBean, Method theMethod) {
|
public void registerFeedback(String theServiceId, Object theServiceBean, Method theMethod) {
|
||||||
final CdsFeedbackMethod cdsFeedbackMethod = new CdsFeedbackMethod(theServiceBean, theMethod);
|
final CdsFeedbackMethod cdsFeedbackMethod = new CdsFeedbackMethod(theServiceBean, theMethod);
|
||||||
myFeedbackMap.put(theServiceId, cdsFeedbackMethod);
|
myFeedbackMap.put(theServiceId, cdsFeedbackMethod);
|
||||||
|
|
|
@ -30,6 +30,8 @@ import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServicesJson;
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServicesJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.ICdsCrServiceFactory;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery.ICrDiscoveryServiceFactory;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchSvc;
|
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchSvc;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
@ -49,14 +51,20 @@ public class CdsServiceRegistryImpl implements ICdsServiceRegistry {
|
||||||
private final CdsHooksContextBooter myCdsHooksContextBooter;
|
private final CdsHooksContextBooter myCdsHooksContextBooter;
|
||||||
private final CdsPrefetchSvc myCdsPrefetchSvc;
|
private final CdsPrefetchSvc myCdsPrefetchSvc;
|
||||||
private final ObjectMapper myObjectMapper;
|
private final ObjectMapper myObjectMapper;
|
||||||
|
private final ICdsCrServiceFactory myCdsCrServiceFactory;
|
||||||
|
private final ICrDiscoveryServiceFactory myCrDiscoveryServiceFactory;
|
||||||
|
|
||||||
public CdsServiceRegistryImpl(
|
public CdsServiceRegistryImpl(
|
||||||
CdsHooksContextBooter theCdsHooksContextBooter,
|
CdsHooksContextBooter theCdsHooksContextBooter,
|
||||||
CdsPrefetchSvc theCdsPrefetchSvc,
|
CdsPrefetchSvc theCdsPrefetchSvc,
|
||||||
ObjectMapper theObjectMapper) {
|
ObjectMapper theObjectMapper,
|
||||||
|
ICdsCrServiceFactory theCdsCrServiceFactory,
|
||||||
|
ICrDiscoveryServiceFactory theCrDiscoveryServiceFactory) {
|
||||||
myCdsHooksContextBooter = theCdsHooksContextBooter;
|
myCdsHooksContextBooter = theCdsHooksContextBooter;
|
||||||
myCdsPrefetchSvc = theCdsPrefetchSvc;
|
myCdsPrefetchSvc = theCdsPrefetchSvc;
|
||||||
myObjectMapper = theObjectMapper;
|
myObjectMapper = theObjectMapper;
|
||||||
|
myCdsCrServiceFactory = theCdsCrServiceFactory;
|
||||||
|
myCrDiscoveryServiceFactory = theCrDiscoveryServiceFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
|
@ -142,6 +150,17 @@ public class CdsServiceRegistryImpl implements ICdsServiceRegistry {
|
||||||
theServiceId, theServiceFunction, theCdsServiceJson, theAllowAutoFhirClientPrefetch, theModuleId);
|
theServiceId, theServiceFunction, theCdsServiceJson, theAllowAutoFhirClientPrefetch, theModuleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean registerCrService(String theServiceId) {
|
||||||
|
try {
|
||||||
|
myServiceCache.registerCrService(theServiceId, myCrDiscoveryServiceFactory, myCdsCrServiceFactory);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ourLog.error("Error received during CR CDS Service registration: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unregisterService(String theServiceId, String theModuleId) {
|
public void unregisterService(String theServiceId, String theModuleId) {
|
||||||
Validate.notNull(theServiceId);
|
Validate.notNull(theServiceId);
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
public class CdsCrConstants {
|
||||||
|
private CdsCrConstants() {}
|
||||||
|
|
||||||
|
public static final String CDS_CR_MODULE_ID = "CR";
|
||||||
|
|
||||||
|
// CDS Hook field names
|
||||||
|
public static final String CDS_PARAMETER_USER_ID = "userId";
|
||||||
|
public static final String CDS_PARAMETER_PATIENT_ID = "patientId";
|
||||||
|
public static final String CDS_PARAMETER_ENCOUNTER_ID = "encounterId";
|
||||||
|
public static final String CDS_PARAMETER_MEDICATIONS = "medications";
|
||||||
|
public static final String CDS_PARAMETER_PERFORMER = "performer";
|
||||||
|
public static final String CDS_PARAMETER_TASK = "task";
|
||||||
|
public static final String CDS_PARAMETER_ORDERS = "orders";
|
||||||
|
public static final String CDS_PARAMETER_SELECTIONS = "selections";
|
||||||
|
public static final String CDS_PARAMETER_DRAFT_ORDERS = "draftOrders";
|
||||||
|
public static final String CDS_PARAMETER_APPOINTMENTS = "appointments";
|
||||||
|
|
||||||
|
// $apply parameter names
|
||||||
|
public static final String APPLY_PARAMETER_PLAN_DEFINITION = "planDefinition";
|
||||||
|
public static final String APPLY_PARAMETER_CANONICAL = "canonical";
|
||||||
|
public static final String APPLY_PARAMETER_SUBJECT = "subject";
|
||||||
|
public static final String APPLY_PARAMETER_PRACTITIONER = "practitioner";
|
||||||
|
public static final String APPLY_PARAMETER_ENCOUNTER = "encounter";
|
||||||
|
public static final String APPLY_PARAMETER_PARAMETERS = "parameters";
|
||||||
|
public static final String APPLY_PARAMETER_DATA = "data";
|
||||||
|
public static final String APPLY_PARAMETER_DATA_ENDPOINT = "dataEndpoint";
|
||||||
|
}
|
|
@ -0,0 +1,291 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.*;
|
||||||
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
|
import org.hl7.fhir.dstu3.model.CarePlan;
|
||||||
|
import org.hl7.fhir.dstu3.model.Endpoint;
|
||||||
|
import org.hl7.fhir.dstu3.model.Extension;
|
||||||
|
import org.hl7.fhir.dstu3.model.IdType;
|
||||||
|
import org.hl7.fhir.dstu3.model.ParameterDefinition;
|
||||||
|
import org.hl7.fhir.dstu3.model.Parameters;
|
||||||
|
import org.hl7.fhir.dstu3.model.PlanDefinition;
|
||||||
|
import org.hl7.fhir.dstu3.model.Reference;
|
||||||
|
import org.hl7.fhir.dstu3.model.RelatedArtifact;
|
||||||
|
import org.hl7.fhir.dstu3.model.RequestGroup;
|
||||||
|
import org.hl7.fhir.dstu3.model.Resource;
|
||||||
|
import org.hl7.fhir.dstu3.model.StringType;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
import org.opencds.cqf.fhir.utility.Canonicals;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_DATA;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_DATA_ENDPOINT;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_ENCOUNTER;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_PARAMETERS;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_PRACTITIONER;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_SUBJECT;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_DRAFT_ORDERS;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_ENCOUNTER_ID;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_PATIENT_ID;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_USER_ID;
|
||||||
|
import static org.opencds.cqf.fhir.utility.dstu3.Parameters.parameters;
|
||||||
|
import static org.opencds.cqf.fhir.utility.dstu3.Parameters.part;
|
||||||
|
|
||||||
|
public class CdsCrServiceDstu3 implements ICdsCrService {
|
||||||
|
protected final RequestDetails myRequestDetails;
|
||||||
|
protected final Repository myRepository;
|
||||||
|
protected CarePlan myResponse;
|
||||||
|
protected CdsServiceResponseJson myServiceResponse;
|
||||||
|
|
||||||
|
public CdsCrServiceDstu3(RequestDetails theRequestDetails, Repository theRepository) {
|
||||||
|
myRequestDetails = theRequestDetails;
|
||||||
|
myRepository = theRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FhirVersionEnum getFhirVersion() {
|
||||||
|
return FhirVersionEnum.DSTU3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Repository getRepository() {
|
||||||
|
return myRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parameters encodeParams(CdsServiceRequestJson theJson) {
|
||||||
|
Parameters parameters = parameters()
|
||||||
|
.addParameter(part(APPLY_PARAMETER_SUBJECT, theJson.getContext().getString(CDS_PARAMETER_PATIENT_ID)));
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_USER_ID)) {
|
||||||
|
parameters.addParameter(
|
||||||
|
part(APPLY_PARAMETER_PRACTITIONER, theJson.getContext().getString(CDS_PARAMETER_USER_ID)));
|
||||||
|
}
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_ENCOUNTER_ID)) {
|
||||||
|
parameters.addParameter(
|
||||||
|
part(APPLY_PARAMETER_ENCOUNTER, theJson.getContext().getString(CDS_PARAMETER_ENCOUNTER_ID)));
|
||||||
|
}
|
||||||
|
var cqlParameters = parameters();
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_DRAFT_ORDERS)) {
|
||||||
|
addCqlParameters(
|
||||||
|
cqlParameters,
|
||||||
|
theJson.getContext().getResource(CDS_PARAMETER_DRAFT_ORDERS),
|
||||||
|
CDS_PARAMETER_DRAFT_ORDERS);
|
||||||
|
}
|
||||||
|
if (cqlParameters.hasParameter()) {
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_PARAMETERS, cqlParameters));
|
||||||
|
}
|
||||||
|
Bundle data = getPrefetchResources(theJson);
|
||||||
|
if (data.hasEntry()) {
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_DATA, data));
|
||||||
|
}
|
||||||
|
if (theJson.getFhirServer() != null) {
|
||||||
|
Endpoint endpoint = new Endpoint().setAddress(theJson.getFhirServer());
|
||||||
|
if (theJson.getServiceRequestAuthorizationJson().getAccessToken() != null) {
|
||||||
|
String tokenType = getTokenType(theJson.getServiceRequestAuthorizationJson());
|
||||||
|
endpoint.addHeader(String.format(
|
||||||
|
"Authorization: %s %s",
|
||||||
|
tokenType, theJson.getServiceRequestAuthorizationJson().getAccessToken()));
|
||||||
|
}
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_DATA_ENDPOINT, endpoint));
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getTokenType(CdsServiceRequestAuthorizationJson theJson) {
|
||||||
|
String tokenType = theJson.getTokenType();
|
||||||
|
return tokenType == null || tokenType.isEmpty() ? "Bearer" : tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Parameters addCqlParameters(
|
||||||
|
Parameters theParameters, IBaseResource theContextResource, String theParamName) {
|
||||||
|
// We are making the assumption that a Library created for a hook will provide parameters for the fields
|
||||||
|
// specified for the hook
|
||||||
|
if (theContextResource instanceof Bundle) {
|
||||||
|
((Bundle) theContextResource)
|
||||||
|
.getEntry()
|
||||||
|
.forEach(x -> theParameters.addParameter(part(theParamName, x.getResource())));
|
||||||
|
} else {
|
||||||
|
theParameters.addParameter(part(theParamName, (Resource) theContextResource));
|
||||||
|
}
|
||||||
|
if (theParameters.getParameter().size() == 1) {
|
||||||
|
Extension listExtension = new Extension(
|
||||||
|
"http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-parameterDefinition",
|
||||||
|
new ParameterDefinition()
|
||||||
|
.setMax("*")
|
||||||
|
.setName(theParameters.getParameterFirstRep().getName()));
|
||||||
|
theParameters.getParameterFirstRep().addExtension(listExtension);
|
||||||
|
}
|
||||||
|
return theParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, Resource> getResourcesFromBundle(Bundle theBundle) {
|
||||||
|
// using HashMap to avoid duplicates
|
||||||
|
Map<String, Resource> resourceMap = new HashMap<>();
|
||||||
|
theBundle
|
||||||
|
.getEntry()
|
||||||
|
.forEach(x -> resourceMap.put(x.fhirType() + x.getResource().getId(), x.getResource()));
|
||||||
|
return resourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Bundle getPrefetchResources(CdsServiceRequestJson theJson) {
|
||||||
|
// using HashMap to avoid duplicates
|
||||||
|
Map<String, Resource> resourceMap = new HashMap<>();
|
||||||
|
Bundle prefetchResources = new Bundle();
|
||||||
|
Resource resource;
|
||||||
|
for (String key : theJson.getPrefetchKeys()) {
|
||||||
|
resource = (Resource) theJson.getPrefetch(key);
|
||||||
|
if (resource == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (resource instanceof Bundle) {
|
||||||
|
resourceMap.putAll(getResourcesFromBundle((Bundle) resource));
|
||||||
|
} else {
|
||||||
|
resourceMap.put(resource.fhirType() + resource.getId(), resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resourceMap.forEach((key, value) -> prefetchResources.addEntry().setResource(value));
|
||||||
|
return prefetchResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceResponseJson encodeResponse(Object theResponse) {
|
||||||
|
assert theResponse instanceof CarePlan;
|
||||||
|
myResponse = (CarePlan) theResponse;
|
||||||
|
CdsServiceResponseJson serviceResponse = new CdsServiceResponseJson();
|
||||||
|
if (myResponse.hasActivity()) {
|
||||||
|
Reference requestGroupRef = myResponse.getActivity().get(0).getReference();
|
||||||
|
RequestGroup mainRequest = (RequestGroup) resolveResource(requestGroupRef);
|
||||||
|
StringType canonical = mainRequest.getDefinition().get(0).getReferenceElement_();
|
||||||
|
PlanDefinition planDef = myRepository.read(
|
||||||
|
PlanDefinition.class,
|
||||||
|
new IdType(Canonicals.getResourceType(canonical), Canonicals.getIdPart(canonical)));
|
||||||
|
List<CdsServiceResponseLinkJson> links = resolvePlanLinks(planDef);
|
||||||
|
mainRequest.getAction().forEach(action -> serviceResponse.addCard(resolveAction(action, links)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<CdsServiceResponseLinkJson> resolvePlanLinks(PlanDefinition thePlanDefinition) {
|
||||||
|
List<CdsServiceResponseLinkJson> links = new ArrayList<>();
|
||||||
|
// links - listed on each card
|
||||||
|
if (thePlanDefinition.hasRelatedArtifact()) {
|
||||||
|
thePlanDefinition.getRelatedArtifact().forEach(ra -> {
|
||||||
|
String linkUrl = ra.getUrl();
|
||||||
|
if (linkUrl != null) {
|
||||||
|
CdsServiceResponseLinkJson link = new CdsServiceResponseLinkJson().setUrl(linkUrl);
|
||||||
|
if (ra.hasDisplay()) {
|
||||||
|
link.setLabel(ra.getDisplay());
|
||||||
|
}
|
||||||
|
if (ra.hasExtension()) {
|
||||||
|
link.setType(ra.getExtensionFirstRep().getValue().primitiveValue());
|
||||||
|
} else link.setType("absolute"); // default
|
||||||
|
links.add(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseCardJson resolveAction(
|
||||||
|
RequestGroup.RequestGroupActionComponent theAction, List<CdsServiceResponseLinkJson> theLinks) {
|
||||||
|
CdsServiceResponseCardJson card = new CdsServiceResponseCardJson()
|
||||||
|
.setSummary(theAction.getTitle())
|
||||||
|
.setDetail(theAction.getDescription())
|
||||||
|
.setLinks(theLinks);
|
||||||
|
|
||||||
|
if (theAction.hasDocumentation()) {
|
||||||
|
card.setSource(resolveSource(theAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theAction.hasSelectionBehavior()) {
|
||||||
|
card.setSelectionBehaviour(theAction.getSelectionBehavior().toCode());
|
||||||
|
theAction.getAction().forEach(action -> resolveSuggestion(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaving this out until the spec details how to map system actions.
|
||||||
|
// if (theAction.hasType() && theAction.hasResource()) {
|
||||||
|
// resolveSystemAction(theAction);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resolveSystemAction(RequestGroup.RequestGroupActionComponent theAction) {
|
||||||
|
if (theAction.hasType()
|
||||||
|
&& theAction.getType().hasCode()
|
||||||
|
&& !theAction.getType().getCode().equals("fire-event")) {
|
||||||
|
myServiceResponse.addServiceAction(new CdsServiceResponseSystemActionJson()
|
||||||
|
.setResource(resolveResource(theAction.getResource()))
|
||||||
|
.setType(theAction.getType().getCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseCardSourceJson resolveSource(RequestGroup.RequestGroupActionComponent theAction) {
|
||||||
|
RelatedArtifact documentation = theAction.getDocumentationFirstRep();
|
||||||
|
CdsServiceResponseCardSourceJson source = new CdsServiceResponseCardSourceJson()
|
||||||
|
.setLabel(documentation.getDisplay())
|
||||||
|
.setUrl(documentation.getUrl());
|
||||||
|
|
||||||
|
if (documentation.hasDocument() && documentation.getDocument().hasUrl()) {
|
||||||
|
source.setIcon(documentation.getDocument().getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseSuggestionJson resolveSuggestion(RequestGroup.RequestGroupActionComponent theAction) {
|
||||||
|
CdsServiceResponseSuggestionJson suggestion = new CdsServiceResponseSuggestionJson()
|
||||||
|
.setLabel(theAction.getTitle())
|
||||||
|
.setUuid(theAction.getId());
|
||||||
|
theAction.getAction().forEach(action -> suggestion.addAction(resolveSuggestionAction(action)));
|
||||||
|
|
||||||
|
return suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseSuggestionActionJson resolveSuggestionAction(
|
||||||
|
RequestGroup.RequestGroupActionComponent theAction) {
|
||||||
|
CdsServiceResponseSuggestionActionJson suggestionAction =
|
||||||
|
new CdsServiceResponseSuggestionActionJson().setDescription(theAction.getDescription());
|
||||||
|
if (theAction.hasType()
|
||||||
|
&& theAction.getType().hasCode()
|
||||||
|
&& !theAction.getType().getCode().equals("fire-event")) {
|
||||||
|
String actionCode = theAction.getType().getCode();
|
||||||
|
suggestionAction.setType(actionCode);
|
||||||
|
}
|
||||||
|
if (theAction.hasResource()) {
|
||||||
|
suggestionAction.setResource(resolveResource(theAction.getResource()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestionAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IBaseResource resolveResource(Reference theReference) {
|
||||||
|
return myResponse.getContained().stream()
|
||||||
|
.filter(resource -> resource.getId().equals(theReference.getReference()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,334 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.*;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.CanonicalType;
|
||||||
|
import org.hl7.fhir.r4.model.Endpoint;
|
||||||
|
import org.hl7.fhir.r4.model.Extension;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.ParameterDefinition;
|
||||||
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
|
import org.hl7.fhir.r4.model.PlanDefinition;
|
||||||
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.hl7.fhir.r4.model.RelatedArtifact;
|
||||||
|
import org.hl7.fhir.r4.model.RequestGroup;
|
||||||
|
import org.hl7.fhir.r4.model.Resource;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
import org.opencds.cqf.fhir.utility.Canonicals;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_DATA;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_DATA_ENDPOINT;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_ENCOUNTER;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_PARAMETERS;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_PRACTITIONER;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_SUBJECT;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_DRAFT_ORDERS;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_ENCOUNTER_ID;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_PATIENT_ID;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_USER_ID;
|
||||||
|
import static org.opencds.cqf.fhir.utility.r4.Parameters.parameters;
|
||||||
|
import static org.opencds.cqf.fhir.utility.r4.Parameters.part;
|
||||||
|
|
||||||
|
public class CdsCrServiceR4 implements ICdsCrService {
|
||||||
|
protected final RequestDetails myRequestDetails;
|
||||||
|
protected final Repository myRepository;
|
||||||
|
protected Bundle myResponseBundle;
|
||||||
|
protected CdsServiceResponseJson myServiceResponse;
|
||||||
|
|
||||||
|
public CdsCrServiceR4(RequestDetails theRequestDetails, Repository theRepository) {
|
||||||
|
myRequestDetails = theRequestDetails;
|
||||||
|
myRepository = theRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FhirVersionEnum getFhirVersion() {
|
||||||
|
return FhirVersionEnum.R4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Repository getRepository() {
|
||||||
|
return myRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parameters encodeParams(CdsServiceRequestJson theJson) {
|
||||||
|
Parameters parameters = parameters()
|
||||||
|
.addParameter(part(APPLY_PARAMETER_SUBJECT, theJson.getContext().getString(CDS_PARAMETER_PATIENT_ID)));
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_USER_ID)) {
|
||||||
|
parameters.addParameter(
|
||||||
|
part(APPLY_PARAMETER_PRACTITIONER, theJson.getContext().getString(CDS_PARAMETER_USER_ID)));
|
||||||
|
}
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_ENCOUNTER_ID)) {
|
||||||
|
parameters.addParameter(
|
||||||
|
part(APPLY_PARAMETER_ENCOUNTER, theJson.getContext().getString(CDS_PARAMETER_ENCOUNTER_ID)));
|
||||||
|
}
|
||||||
|
var cqlParameters = parameters();
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_DRAFT_ORDERS)) {
|
||||||
|
addCqlParameters(
|
||||||
|
cqlParameters,
|
||||||
|
theJson.getContext().getResource(CDS_PARAMETER_DRAFT_ORDERS),
|
||||||
|
CDS_PARAMETER_DRAFT_ORDERS);
|
||||||
|
}
|
||||||
|
if (cqlParameters.hasParameter()) {
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_PARAMETERS, cqlParameters));
|
||||||
|
}
|
||||||
|
Bundle data = getPrefetchResources(theJson);
|
||||||
|
if (data.hasEntry()) {
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_DATA, data));
|
||||||
|
}
|
||||||
|
if (theJson.getFhirServer() != null) {
|
||||||
|
Endpoint endpoint = new Endpoint().setAddress(theJson.getFhirServer());
|
||||||
|
if (theJson.getServiceRequestAuthorizationJson().getAccessToken() != null) {
|
||||||
|
String tokenType = getTokenType(theJson.getServiceRequestAuthorizationJson());
|
||||||
|
endpoint.addHeader(String.format(
|
||||||
|
"Authorization: %s %s",
|
||||||
|
tokenType, theJson.getServiceRequestAuthorizationJson().getAccessToken()));
|
||||||
|
}
|
||||||
|
endpoint.addHeader("Epic-Client-ID: 2cb5af9f-f483-4e2a-aedc-54c3a31cb153");
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_DATA_ENDPOINT, endpoint));
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getTokenType(CdsServiceRequestAuthorizationJson theJson) {
|
||||||
|
String tokenType = theJson.getTokenType();
|
||||||
|
return tokenType == null || tokenType.isEmpty() ? "Bearer" : tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Parameters addCqlParameters(
|
||||||
|
Parameters theParameters, IBaseResource theContextResource, String theParamName) {
|
||||||
|
// We are making the assumption that a Library created for a hook will provide parameters for the fields
|
||||||
|
// specified for the hook
|
||||||
|
if (theContextResource instanceof Bundle) {
|
||||||
|
((Bundle) theContextResource)
|
||||||
|
.getEntry()
|
||||||
|
.forEach(x -> theParameters.addParameter(part(theParamName, x.getResource())));
|
||||||
|
} else {
|
||||||
|
theParameters.addParameter(part(theParamName, (Resource) theContextResource));
|
||||||
|
}
|
||||||
|
if (theParameters.getParameter().size() == 1) {
|
||||||
|
Extension listExtension = new Extension(
|
||||||
|
"http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-parameterDefinition",
|
||||||
|
new ParameterDefinition()
|
||||||
|
.setMax("*")
|
||||||
|
.setName(theParameters.getParameterFirstRep().getName()));
|
||||||
|
theParameters.getParameterFirstRep().addExtension(listExtension);
|
||||||
|
}
|
||||||
|
return theParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, Resource> getResourcesFromBundle(Bundle theBundle) {
|
||||||
|
// using HashMap to avoid duplicates
|
||||||
|
Map<String, Resource> resourceMap = new HashMap<>();
|
||||||
|
theBundle
|
||||||
|
.getEntry()
|
||||||
|
.forEach(x -> resourceMap.put(x.fhirType() + x.getResource().getId(), x.getResource()));
|
||||||
|
return resourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Bundle getPrefetchResources(CdsServiceRequestJson theJson) {
|
||||||
|
// using HashMap to avoid duplicates
|
||||||
|
Map<String, Resource> resourceMap = new HashMap<>();
|
||||||
|
Bundle prefetchResources = new Bundle();
|
||||||
|
Resource resource;
|
||||||
|
for (String key : theJson.getPrefetchKeys()) {
|
||||||
|
resource = (Resource) theJson.getPrefetch(key);
|
||||||
|
if (resource == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (resource instanceof Bundle) {
|
||||||
|
resourceMap.putAll(getResourcesFromBundle((Bundle) resource));
|
||||||
|
} else {
|
||||||
|
resourceMap.put(resource.fhirType() + resource.getId(), resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resourceMap.forEach((key, value) -> prefetchResources.addEntry().setResource(value));
|
||||||
|
return prefetchResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceResponseJson encodeResponse(Object theResponse) {
|
||||||
|
assert theResponse instanceof Bundle;
|
||||||
|
myResponseBundle = (Bundle) theResponse;
|
||||||
|
myServiceResponse = new CdsServiceResponseJson();
|
||||||
|
if (myResponseBundle.hasEntry()) {
|
||||||
|
RequestGroup mainRequest =
|
||||||
|
(RequestGroup) myResponseBundle.getEntry().get(0).getResource();
|
||||||
|
CanonicalType canonical = mainRequest.getInstantiatesCanonical().get(0);
|
||||||
|
PlanDefinition planDef = myRepository.read(
|
||||||
|
PlanDefinition.class,
|
||||||
|
new IdType(Canonicals.getResourceType(canonical), Canonicals.getIdPart(canonical)));
|
||||||
|
List<CdsServiceResponseLinkJson> links = resolvePlanLinks(planDef);
|
||||||
|
mainRequest.getAction().forEach(action -> myServiceResponse.addCard(resolveAction(action, links)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return myServiceResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<CdsServiceResponseLinkJson> resolvePlanLinks(PlanDefinition thePlanDefinition) {
|
||||||
|
List<CdsServiceResponseLinkJson> links = new ArrayList<>();
|
||||||
|
// links - listed on each card
|
||||||
|
if (thePlanDefinition.hasRelatedArtifact()) {
|
||||||
|
thePlanDefinition.getRelatedArtifact().forEach(ra -> {
|
||||||
|
String linkUrl = ra.getUrl();
|
||||||
|
if (linkUrl != null) {
|
||||||
|
CdsServiceResponseLinkJson link = new CdsServiceResponseLinkJson().setUrl(linkUrl);
|
||||||
|
if (ra.hasDisplay()) {
|
||||||
|
link.setLabel(ra.getDisplay());
|
||||||
|
}
|
||||||
|
if (ra.hasExtension()) {
|
||||||
|
link.setType(ra.getExtensionFirstRep().getValue().primitiveValue());
|
||||||
|
} else link.setType("absolute"); // default
|
||||||
|
links.add(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseCardJson resolveAction(
|
||||||
|
RequestGroup.RequestGroupActionComponent theAction, List<CdsServiceResponseLinkJson> theLinks) {
|
||||||
|
CdsServiceResponseCardJson card = new CdsServiceResponseCardJson()
|
||||||
|
.setSummary(theAction.getTitle())
|
||||||
|
.setDetail(theAction.getDescription())
|
||||||
|
.setLinks(theLinks);
|
||||||
|
|
||||||
|
if (theAction.hasPriority()) {
|
||||||
|
card.setIndicator(resolveIndicator(theAction.getPriority().toCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theAction.hasDocumentation()) {
|
||||||
|
card.setSource(resolveSource(theAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theAction.hasSelectionBehavior()) {
|
||||||
|
card.setSelectionBehaviour(theAction.getSelectionBehavior().toCode());
|
||||||
|
theAction.getAction().forEach(action -> resolveSuggestion(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaving this out until the spec details how to map system actions.
|
||||||
|
// if (theAction.hasType() && theAction.hasResource()) {
|
||||||
|
// resolveSystemAction(theAction);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceIndicatorEnum resolveIndicator(String theCode) {
|
||||||
|
CdsServiceIndicatorEnum indicator;
|
||||||
|
switch (theCode) {
|
||||||
|
case "routine":
|
||||||
|
indicator = CdsServiceIndicatorEnum.INFO;
|
||||||
|
break;
|
||||||
|
case "urgent":
|
||||||
|
indicator = CdsServiceIndicatorEnum.WARNING;
|
||||||
|
break;
|
||||||
|
case "stat":
|
||||||
|
indicator = CdsServiceIndicatorEnum.CRITICAL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
indicator = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (indicator == null) {
|
||||||
|
// Code 2435-2440 are reserved for this error message across versions
|
||||||
|
throw new IllegalArgumentException(Msg.code(2435) + "Invalid priority code: " + theCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return indicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resolveSystemAction(RequestGroup.RequestGroupActionComponent theAction) {
|
||||||
|
if (theAction.hasType()
|
||||||
|
&& theAction.getType().hasCoding()
|
||||||
|
&& theAction.getType().getCodingFirstRep().hasCode()
|
||||||
|
&& !theAction.getType().getCodingFirstRep().getCode().equals("fire-event")) {
|
||||||
|
myServiceResponse.addServiceAction(new CdsServiceResponseSystemActionJson()
|
||||||
|
.setResource(resolveResource(theAction.getResource()))
|
||||||
|
.setType(theAction.getType().getCodingFirstRep().getCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseCardSourceJson resolveSource(RequestGroup.RequestGroupActionComponent theAction) {
|
||||||
|
RelatedArtifact documentation = theAction.getDocumentationFirstRep();
|
||||||
|
CdsServiceResponseCardSourceJson source = new CdsServiceResponseCardSourceJson()
|
||||||
|
.setLabel(documentation.getDisplay())
|
||||||
|
.setUrl(documentation.getUrl());
|
||||||
|
|
||||||
|
if (documentation.hasDocument() && documentation.getDocument().hasUrl()) {
|
||||||
|
source.setIcon(documentation.getDocument().getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseSuggestionJson resolveSuggestion(RequestGroup.RequestGroupActionComponent theAction) {
|
||||||
|
CdsServiceResponseSuggestionJson suggestion = new CdsServiceResponseSuggestionJson()
|
||||||
|
.setLabel(theAction.getTitle())
|
||||||
|
.setUuid(theAction.getId());
|
||||||
|
theAction.getAction().forEach(action -> suggestion.addAction(resolveSuggestionAction(action)));
|
||||||
|
|
||||||
|
return suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseSuggestionActionJson resolveSuggestionAction(
|
||||||
|
RequestGroup.RequestGroupActionComponent theAction) {
|
||||||
|
CdsServiceResponseSuggestionActionJson suggestionAction =
|
||||||
|
new CdsServiceResponseSuggestionActionJson().setDescription(theAction.getDescription());
|
||||||
|
if (theAction.hasType()
|
||||||
|
&& theAction.getType().hasCoding()
|
||||||
|
&& theAction.getType().getCodingFirstRep().hasCode()
|
||||||
|
&& !theAction.getType().getCodingFirstRep().getCode().equals("fire-event")) {
|
||||||
|
String actionCode = theAction.getType().getCodingFirstRep().getCode();
|
||||||
|
suggestionAction.setType(actionCode);
|
||||||
|
}
|
||||||
|
if (theAction.hasResource()) {
|
||||||
|
suggestionAction.setResource(resolveResource(theAction.getResource()));
|
||||||
|
// Leaving this out until the spec details how to map system actions.
|
||||||
|
// if (!suggestionAction.getType().isEmpty()) {
|
||||||
|
// resolveSystemAction(theAction);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestionAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IBaseResource resolveResource(Reference theReference) {
|
||||||
|
String reference = theReference.getReference();
|
||||||
|
String[] split = reference.split("/");
|
||||||
|
String id = reference.contains("/") ? split[1] : reference;
|
||||||
|
String resourceType = reference.contains("/") ? split[0] : theReference.getType();
|
||||||
|
List<IBaseResource> results = myResponseBundle.getEntry().stream()
|
||||||
|
.filter(entry -> entry.hasResource()
|
||||||
|
&& entry.getResource().getResourceType().toString().equals(resourceType)
|
||||||
|
&& entry.getResource().getIdPart().equals(id))
|
||||||
|
.map(entry -> entry.getResource())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return results.isEmpty() ? null : results.get(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,337 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.*;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.r5.model.Bundle;
|
||||||
|
import org.hl7.fhir.r5.model.CanonicalType;
|
||||||
|
import org.hl7.fhir.r5.model.Endpoint;
|
||||||
|
import org.hl7.fhir.r5.model.Extension;
|
||||||
|
import org.hl7.fhir.r5.model.IdType;
|
||||||
|
import org.hl7.fhir.r5.model.ParameterDefinition;
|
||||||
|
import org.hl7.fhir.r5.model.Parameters;
|
||||||
|
import org.hl7.fhir.r5.model.PlanDefinition;
|
||||||
|
import org.hl7.fhir.r5.model.Reference;
|
||||||
|
import org.hl7.fhir.r5.model.RelatedArtifact;
|
||||||
|
import org.hl7.fhir.r5.model.RequestOrchestration;
|
||||||
|
import org.hl7.fhir.r5.model.Resource;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
import org.opencds.cqf.fhir.utility.Canonicals;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_DATA;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_DATA_ENDPOINT;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_ENCOUNTER;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_PARAMETERS;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_PRACTITIONER;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.APPLY_PARAMETER_SUBJECT;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_DRAFT_ORDERS;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_ENCOUNTER_ID;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_PATIENT_ID;
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_PARAMETER_USER_ID;
|
||||||
|
import static org.opencds.cqf.fhir.utility.r5.Parameters.parameters;
|
||||||
|
import static org.opencds.cqf.fhir.utility.r5.Parameters.part;
|
||||||
|
|
||||||
|
public class CdsCrServiceR5 implements ICdsCrService {
|
||||||
|
protected final RequestDetails myRequestDetails;
|
||||||
|
protected final Repository myRepository;
|
||||||
|
protected Bundle myResponseBundle;
|
||||||
|
protected CdsServiceResponseJson myServiceResponse;
|
||||||
|
|
||||||
|
public CdsCrServiceR5(RequestDetails theRequestDetails, Repository theRepository) {
|
||||||
|
myRequestDetails = theRequestDetails;
|
||||||
|
myRepository = theRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FhirVersionEnum getFhirVersion() {
|
||||||
|
return FhirVersionEnum.R5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Repository getRepository() {
|
||||||
|
return myRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parameters encodeParams(CdsServiceRequestJson theJson) {
|
||||||
|
Parameters parameters = parameters()
|
||||||
|
.addParameter(part(APPLY_PARAMETER_SUBJECT, theJson.getContext().getString(CDS_PARAMETER_PATIENT_ID)));
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_USER_ID)) {
|
||||||
|
parameters.addParameter(
|
||||||
|
part(APPLY_PARAMETER_PRACTITIONER, theJson.getContext().getString(CDS_PARAMETER_USER_ID)));
|
||||||
|
}
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_ENCOUNTER_ID)) {
|
||||||
|
parameters.addParameter(
|
||||||
|
part(APPLY_PARAMETER_ENCOUNTER, theJson.getContext().getString(CDS_PARAMETER_ENCOUNTER_ID)));
|
||||||
|
}
|
||||||
|
var cqlParameters = parameters();
|
||||||
|
if (theJson.getContext().containsKey(CDS_PARAMETER_DRAFT_ORDERS)) {
|
||||||
|
addCqlParameters(
|
||||||
|
cqlParameters,
|
||||||
|
theJson.getContext().getResource(CDS_PARAMETER_DRAFT_ORDERS),
|
||||||
|
CDS_PARAMETER_DRAFT_ORDERS);
|
||||||
|
}
|
||||||
|
if (cqlParameters.hasParameter()) {
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_PARAMETERS, cqlParameters));
|
||||||
|
}
|
||||||
|
Bundle data = getPrefetchResources(theJson);
|
||||||
|
if (data.hasEntry()) {
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_DATA, data));
|
||||||
|
}
|
||||||
|
if (theJson.getFhirServer() != null) {
|
||||||
|
Endpoint endpoint = new Endpoint().setAddress(theJson.getFhirServer());
|
||||||
|
if (theJson.getServiceRequestAuthorizationJson().getAccessToken() != null) {
|
||||||
|
String tokenType = getTokenType(theJson.getServiceRequestAuthorizationJson());
|
||||||
|
endpoint.addHeader(String.format(
|
||||||
|
"Authorization: %s %s",
|
||||||
|
tokenType, theJson.getServiceRequestAuthorizationJson().getAccessToken()));
|
||||||
|
}
|
||||||
|
parameters.addParameter(part(APPLY_PARAMETER_DATA_ENDPOINT, endpoint));
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getTokenType(CdsServiceRequestAuthorizationJson theJson) {
|
||||||
|
String tokenType = theJson.getTokenType();
|
||||||
|
return tokenType == null || tokenType.isEmpty() ? "Bearer" : tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Parameters addCqlParameters(
|
||||||
|
Parameters theParameters, IBaseResource theContextResource, String theParamName) {
|
||||||
|
// We are making the assumption that a Library created for a hook will provide parameters for the fields
|
||||||
|
// specified for the hook
|
||||||
|
if (theContextResource instanceof Bundle) {
|
||||||
|
((Bundle) theContextResource)
|
||||||
|
.getEntry()
|
||||||
|
.forEach(x -> theParameters.addParameter(part(theParamName, x.getResource())));
|
||||||
|
} else {
|
||||||
|
theParameters.addParameter(part(theParamName, (Resource) theContextResource));
|
||||||
|
}
|
||||||
|
if (theParameters.getParameter().size() == 1) {
|
||||||
|
Extension listExtension = new Extension(
|
||||||
|
"http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-parameterDefinition",
|
||||||
|
new ParameterDefinition()
|
||||||
|
.setMax("*")
|
||||||
|
.setName(theParameters.getParameterFirstRep().getName()));
|
||||||
|
theParameters.getParameterFirstRep().addExtension(listExtension);
|
||||||
|
}
|
||||||
|
return theParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, Resource> getResourcesFromBundle(Bundle theBundle) {
|
||||||
|
// using HashMap to avoid duplicates
|
||||||
|
Map<String, Resource> resourceMap = new HashMap<>();
|
||||||
|
theBundle
|
||||||
|
.getEntry()
|
||||||
|
.forEach(x -> resourceMap.put(x.fhirType() + x.getResource().getId(), x.getResource()));
|
||||||
|
return resourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Bundle getPrefetchResources(CdsServiceRequestJson theJson) {
|
||||||
|
// using HashMap to avoid duplicates
|
||||||
|
Map<String, Resource> resourceMap = new HashMap<>();
|
||||||
|
Bundle prefetchResources = new Bundle();
|
||||||
|
Resource resource;
|
||||||
|
for (String key : theJson.getPrefetchKeys()) {
|
||||||
|
resource = (Resource) theJson.getPrefetch(key);
|
||||||
|
if (resource == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (resource instanceof Bundle) {
|
||||||
|
resourceMap.putAll(getResourcesFromBundle((Bundle) resource));
|
||||||
|
} else {
|
||||||
|
resourceMap.put(resource.fhirType() + resource.getId(), resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resourceMap.forEach((key, value) -> prefetchResources.addEntry().setResource(value));
|
||||||
|
return prefetchResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceResponseJson encodeResponse(Object theResponse) {
|
||||||
|
assert theResponse instanceof Bundle;
|
||||||
|
myResponseBundle = (Bundle) theResponse;
|
||||||
|
CdsServiceResponseJson serviceResponse = new CdsServiceResponseJson();
|
||||||
|
if (myResponseBundle.hasEntry()) {
|
||||||
|
RequestOrchestration mainRequest =
|
||||||
|
(RequestOrchestration) myResponseBundle.getEntry().get(0).getResource();
|
||||||
|
CanonicalType canonical = mainRequest.getInstantiatesCanonical().get(0);
|
||||||
|
PlanDefinition planDef = myRepository.read(
|
||||||
|
PlanDefinition.class,
|
||||||
|
new IdType(Canonicals.getResourceType(canonical), Canonicals.getIdPart(canonical)));
|
||||||
|
List<CdsServiceResponseLinkJson> links = resolvePlanLinks(planDef);
|
||||||
|
mainRequest.getAction().forEach(action -> serviceResponse.addCard(resolveAction(action, links)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<CdsServiceResponseLinkJson> resolvePlanLinks(PlanDefinition thePlanDefinition) {
|
||||||
|
List<CdsServiceResponseLinkJson> links = new ArrayList<>();
|
||||||
|
// links - listed on each card
|
||||||
|
if (thePlanDefinition.hasRelatedArtifact()) {
|
||||||
|
thePlanDefinition.getRelatedArtifact().forEach(ra -> {
|
||||||
|
String linkUrl = ra.getDocument().getUrl();
|
||||||
|
if (linkUrl != null) {
|
||||||
|
CdsServiceResponseLinkJson link = new CdsServiceResponseLinkJson().setUrl(linkUrl);
|
||||||
|
if (ra.hasDisplay()) {
|
||||||
|
link.setLabel(ra.getDisplay());
|
||||||
|
}
|
||||||
|
if (ra.hasExtension()) {
|
||||||
|
link.setType(ra.getExtensionFirstRep().getValue().primitiveValue());
|
||||||
|
} else link.setType("absolute"); // default
|
||||||
|
links.add(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseCardJson resolveAction(
|
||||||
|
RequestOrchestration.RequestOrchestrationActionComponent theAction,
|
||||||
|
List<CdsServiceResponseLinkJson> theLinks) {
|
||||||
|
CdsServiceResponseCardJson card = new CdsServiceResponseCardJson()
|
||||||
|
.setSummary(theAction.getTitle())
|
||||||
|
.setDetail(theAction.getDescription())
|
||||||
|
.setLinks(theLinks);
|
||||||
|
|
||||||
|
if (theAction.hasPriority()) {
|
||||||
|
card.setIndicator(resolveIndicator(theAction.getPriority().toCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theAction.hasDocumentation()) {
|
||||||
|
card.setSource(resolveSource(theAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theAction.hasSelectionBehavior()) {
|
||||||
|
card.setSelectionBehaviour(theAction.getSelectionBehavior().toCode());
|
||||||
|
theAction.getAction().forEach(action -> resolveSuggestion(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaving this out until the spec details how to map system actions.
|
||||||
|
// if (theAction.hasType() && theAction.hasResource()) {
|
||||||
|
// resolveSystemAction(theAction);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceIndicatorEnum resolveIndicator(String theCode) {
|
||||||
|
CdsServiceIndicatorEnum indicator;
|
||||||
|
switch (theCode) {
|
||||||
|
case "routine":
|
||||||
|
indicator = CdsServiceIndicatorEnum.INFO;
|
||||||
|
break;
|
||||||
|
case "urgent":
|
||||||
|
indicator = CdsServiceIndicatorEnum.WARNING;
|
||||||
|
break;
|
||||||
|
case "stat":
|
||||||
|
indicator = CdsServiceIndicatorEnum.CRITICAL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
indicator = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (indicator == null) {
|
||||||
|
// Code 2435-2440 are reserved for this error message across versions
|
||||||
|
throw new IllegalArgumentException(Msg.code(2436) + "Invalid priority code: " + theCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return indicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resolveSystemAction(RequestOrchestration.RequestOrchestrationActionComponent theAction) {
|
||||||
|
if (theAction.hasType()
|
||||||
|
&& theAction.getType().hasCoding()
|
||||||
|
&& theAction.getType().getCodingFirstRep().hasCode()
|
||||||
|
&& !theAction.getType().getCodingFirstRep().getCode().equals("fire-event")) {
|
||||||
|
myServiceResponse.addServiceAction(new CdsServiceResponseSystemActionJson()
|
||||||
|
.setResource(resolveResource(theAction.getResource()))
|
||||||
|
.setType(theAction.getType().getCodingFirstRep().getCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseCardSourceJson resolveSource(
|
||||||
|
RequestOrchestration.RequestOrchestrationActionComponent theAction) {
|
||||||
|
RelatedArtifact documentation = theAction.getDocumentationFirstRep();
|
||||||
|
CdsServiceResponseCardSourceJson source = new CdsServiceResponseCardSourceJson()
|
||||||
|
.setLabel(documentation.getDisplay())
|
||||||
|
.setUrl(documentation.getDocument().getUrl());
|
||||||
|
|
||||||
|
// If we use the document for the url, what do we use for the icon?
|
||||||
|
// if (documentation.hasDocument() && documentation.getDocument().hasUrl()) {
|
||||||
|
// source.setIcon(documentation.getDocument().getUrl());
|
||||||
|
// }
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseSuggestionJson resolveSuggestion(
|
||||||
|
RequestOrchestration.RequestOrchestrationActionComponent theAction) {
|
||||||
|
CdsServiceResponseSuggestionJson suggestion = new CdsServiceResponseSuggestionJson()
|
||||||
|
.setLabel(theAction.getTitle())
|
||||||
|
.setUuid(theAction.getId());
|
||||||
|
theAction.getAction().forEach(action -> suggestion.addAction(resolveSuggestionAction(action)));
|
||||||
|
|
||||||
|
return suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceResponseSuggestionActionJson resolveSuggestionAction(
|
||||||
|
RequestOrchestration.RequestOrchestrationActionComponent theAction) {
|
||||||
|
CdsServiceResponseSuggestionActionJson suggestionAction =
|
||||||
|
new CdsServiceResponseSuggestionActionJson().setDescription(theAction.getDescription());
|
||||||
|
if (theAction.hasType()
|
||||||
|
&& theAction.getType().hasCoding()
|
||||||
|
&& theAction.getType().getCodingFirstRep().hasCode()
|
||||||
|
&& !theAction.getType().getCodingFirstRep().getCode().equals("fire-event")) {
|
||||||
|
String actionCode = theAction.getType().getCodingFirstRep().getCode();
|
||||||
|
suggestionAction.setType(actionCode);
|
||||||
|
}
|
||||||
|
if (theAction.hasResource()) {
|
||||||
|
suggestionAction.setResource(resolveResource(theAction.getResource()));
|
||||||
|
// Leaving this out until the spec details how to map system actions.
|
||||||
|
// if (!suggestionAction.getType().isEmpty()) {
|
||||||
|
// resolveSystemAction(theAction);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestionAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IBaseResource resolveResource(Reference theReference) {
|
||||||
|
String reference = theReference.getReference();
|
||||||
|
String[] split = reference.split("/");
|
||||||
|
String id = reference.contains("/") ? split[1] : reference;
|
||||||
|
String resourceType = reference.contains("/") ? split[0] : theReference.getType();
|
||||||
|
List<IBaseResource> results = myResponseBundle.getEntry().stream()
|
||||||
|
.filter(entry -> entry.hasResource()
|
||||||
|
&& entry.getResource().getResourceType().toString().equals(resourceType)
|
||||||
|
&& entry.getResource().getIdPart().equals(id))
|
||||||
|
.map(entry -> entry.getResource())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return results.isEmpty() ? null : results.get(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
|
||||||
|
public class CdsCrUtils {
|
||||||
|
public static IBaseResource readPlanDefinitionFromRepository(
|
||||||
|
FhirVersionEnum theFhirVersion, Repository theRepository, IIdType theId) {
|
||||||
|
switch (theFhirVersion) {
|
||||||
|
case DSTU3:
|
||||||
|
return theRepository.read(org.hl7.fhir.dstu3.model.PlanDefinition.class, theId);
|
||||||
|
case R4:
|
||||||
|
return theRepository.read(org.hl7.fhir.r4.model.PlanDefinition.class, theId);
|
||||||
|
case R5:
|
||||||
|
return theRepository.read(org.hl7.fhir.r5.model.PlanDefinition.class, theId);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.cache.IResourceChangeEvent;
|
||||||
|
import ca.uhn.fhir.jpa.cache.IResourceChangeListener;
|
||||||
|
import ca.uhn.fhir.jpa.cache.ResourceChangeEvent;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.CdsServiceRegistryImpl;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CDS_CR_MODULE_ID;
|
||||||
|
|
||||||
|
public class CdsServiceInterceptor implements IResourceChangeListener {
|
||||||
|
static final Logger ourLog = LoggerFactory.getLogger(CdsServiceInterceptor.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
CdsServiceRegistryImpl myCdsServiceRegistry;
|
||||||
|
|
||||||
|
public CdsServiceInterceptor() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleInit(Collection<IIdType> theResourceIds) {
|
||||||
|
handleChange(ResourceChangeEvent.fromCreatedUpdatedDeletedResourceIds(
|
||||||
|
new ArrayList<>(theResourceIds), Collections.emptyList(), Collections.emptyList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleChange(IResourceChangeEvent theResourceChangeEvent) {
|
||||||
|
if (theResourceChangeEvent == null) return;
|
||||||
|
if (theResourceChangeEvent.getCreatedResourceIds() != null
|
||||||
|
&& !theResourceChangeEvent.getCreatedResourceIds().isEmpty()) {
|
||||||
|
insert(theResourceChangeEvent.getCreatedResourceIds());
|
||||||
|
}
|
||||||
|
if (theResourceChangeEvent.getUpdatedResourceIds() != null
|
||||||
|
&& !theResourceChangeEvent.getUpdatedResourceIds().isEmpty()) {
|
||||||
|
update(theResourceChangeEvent.getUpdatedResourceIds());
|
||||||
|
}
|
||||||
|
if (theResourceChangeEvent.getDeletedResourceIds() != null
|
||||||
|
&& !theResourceChangeEvent.getDeletedResourceIds().isEmpty()) {
|
||||||
|
delete(theResourceChangeEvent.getDeletedResourceIds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insert(List<IIdType> theCreatedIds) {
|
||||||
|
for (IIdType id : theCreatedIds) {
|
||||||
|
try {
|
||||||
|
myCdsServiceRegistry.registerCrService(id.getIdPart());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ourLog.info(String.format("Failed to create service for %s", id.getIdPart()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(List<IIdType> updatedIds) {
|
||||||
|
try {
|
||||||
|
delete(updatedIds);
|
||||||
|
insert(updatedIds);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ourLog.info(String.format("Failed to update service(s) for %s", updatedIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(List<IIdType> deletedIds) {
|
||||||
|
for (IIdType id : deletedIds) {
|
||||||
|
myCdsServiceRegistry.unregisterService(id.getIdPart(), CDS_CR_MODULE_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.model.api.IModelJson;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public interface ICdsCrService {
|
||||||
|
IBaseParameters encodeParams(CdsServiceRequestJson theJson);
|
||||||
|
|
||||||
|
CdsServiceResponseJson encodeResponse(Object theResponse);
|
||||||
|
|
||||||
|
FhirVersionEnum getFhirVersion();
|
||||||
|
|
||||||
|
Repository getRepository();
|
||||||
|
|
||||||
|
default Object invoke(IModelJson theJson) {
|
||||||
|
IBaseParameters params = encodeParams((CdsServiceRequestJson) theJson);
|
||||||
|
IBaseResource response = invokeApply(params);
|
||||||
|
return encodeResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
default IBaseResource invokeApply(IBaseParameters theParams) {
|
||||||
|
var operationName = getFhirVersion() == FhirVersionEnum.R4
|
||||||
|
? ProviderConstants.CR_OPERATION_R5_APPLY
|
||||||
|
: ProviderConstants.CR_OPERATION_APPLY;
|
||||||
|
switch (getFhirVersion()) {
|
||||||
|
case DSTU3:
|
||||||
|
return getRepository()
|
||||||
|
.invoke(
|
||||||
|
org.hl7.fhir.dstu3.model.PlanDefinition.class,
|
||||||
|
operationName,
|
||||||
|
theParams,
|
||||||
|
org.hl7.fhir.dstu3.model.CarePlan.class,
|
||||||
|
Collections.singletonMap(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON));
|
||||||
|
case R4:
|
||||||
|
return getRepository()
|
||||||
|
.invoke(
|
||||||
|
org.hl7.fhir.r4.model.PlanDefinition.class,
|
||||||
|
operationName,
|
||||||
|
theParams,
|
||||||
|
org.hl7.fhir.r4.model.Bundle.class,
|
||||||
|
Collections.singletonMap(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON));
|
||||||
|
case R5:
|
||||||
|
return getRepository()
|
||||||
|
.invoke(
|
||||||
|
org.hl7.fhir.r5.model.PlanDefinition.class,
|
||||||
|
operationName,
|
||||||
|
theParams,
|
||||||
|
org.hl7.fhir.r5.model.Bundle.class,
|
||||||
|
Collections.singletonMap(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON));
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
public interface ICdsCrServiceFactory {
|
||||||
|
ICdsCrService create(String theServiceId);
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.CdsResolutionStrategyEnum;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
import org.hl7.fhir.dstu3.model.PlanDefinition;
|
||||||
|
import org.hl7.fhir.r4.model.TriggerDefinition;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CrDiscoveryElementDstu3 implements ICrDiscoveryElement {
|
||||||
|
protected PlanDefinition myPlanDefinition;
|
||||||
|
protected PrefetchUrlList myPrefetchUrlList;
|
||||||
|
|
||||||
|
public CrDiscoveryElementDstu3(PlanDefinition thePlanDefinition, PrefetchUrlList thePrefetchUrlList) {
|
||||||
|
myPlanDefinition = thePlanDefinition;
|
||||||
|
myPrefetchUrlList = thePrefetchUrlList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceJson getCdsServiceJson() {
|
||||||
|
if (myPlanDefinition == null
|
||||||
|
|| !myPlanDefinition.hasAction()
|
||||||
|
|| myPlanDefinition.getAction().stream().noneMatch(a -> a.hasTriggerDefinition())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var triggerDefs = myPlanDefinition.getAction().stream()
|
||||||
|
.filter(a -> a.hasTriggerDefinition())
|
||||||
|
.flatMap(a -> a.getTriggerDefinition().stream())
|
||||||
|
.filter(t -> t.getType().equals(TriggerDefinition.TriggerType.NAMEDEVENT))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (triggerDefs == null || triggerDefs.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var service = new CdsServiceJson()
|
||||||
|
.setId(myPlanDefinition.getIdElement().getIdPart())
|
||||||
|
.setTitle(myPlanDefinition.getTitle())
|
||||||
|
.setDescription(myPlanDefinition.getDescription())
|
||||||
|
.setHook(triggerDefs.get(0).getEventName());
|
||||||
|
|
||||||
|
if (myPrefetchUrlList == null) {
|
||||||
|
myPrefetchUrlList = new PrefetchUrlList();
|
||||||
|
}
|
||||||
|
|
||||||
|
int itemNo = 0;
|
||||||
|
if (!myPrefetchUrlList.stream()
|
||||||
|
.anyMatch(p -> p.equals("Patient/{{context.patientId}}")
|
||||||
|
|| p.equals("Patient?_id={{context.patientId}}")
|
||||||
|
|| p.equals("Patient?_id=Patient/{{context.patientId}}"))) {
|
||||||
|
String key = getKey(++itemNo);
|
||||||
|
service.addPrefetch(key, "Patient?_id={{context.patientId}}");
|
||||||
|
service.addSource(key, CdsResolutionStrategyEnum.SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String item : myPrefetchUrlList) {
|
||||||
|
String key = getKey(++itemNo);
|
||||||
|
service.addPrefetch(key, item);
|
||||||
|
service.addSource(key, CdsResolutionStrategyEnum.SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.CdsResolutionStrategyEnum;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
import org.hl7.fhir.r4.model.PlanDefinition;
|
||||||
|
import org.hl7.fhir.r4.model.TriggerDefinition;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CrDiscoveryElementR4 implements ICrDiscoveryElement {
|
||||||
|
protected PlanDefinition myPlanDefinition;
|
||||||
|
protected PrefetchUrlList myPrefetchUrlList;
|
||||||
|
|
||||||
|
public CrDiscoveryElementR4(PlanDefinition thePlanDefinition, PrefetchUrlList thePrefetchUrlList) {
|
||||||
|
myPlanDefinition = thePlanDefinition;
|
||||||
|
myPrefetchUrlList = thePrefetchUrlList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceJson getCdsServiceJson() {
|
||||||
|
if (myPlanDefinition == null
|
||||||
|
|| !myPlanDefinition.hasAction()
|
||||||
|
|| myPlanDefinition.getAction().stream().noneMatch(a -> a.hasTrigger())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var triggerDefs = myPlanDefinition.getAction().stream()
|
||||||
|
.filter(a -> a.hasTrigger())
|
||||||
|
.flatMap(a -> a.getTrigger().stream())
|
||||||
|
.filter(t -> t.getType().equals(TriggerDefinition.TriggerType.NAMEDEVENT))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (triggerDefs == null || triggerDefs.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var service = new CdsServiceJson()
|
||||||
|
.setId(myPlanDefinition.getIdElement().getIdPart())
|
||||||
|
.setTitle(myPlanDefinition.getTitle())
|
||||||
|
.setDescription(myPlanDefinition.getDescription())
|
||||||
|
.setHook(triggerDefs.get(0).getName());
|
||||||
|
|
||||||
|
if (myPrefetchUrlList == null) {
|
||||||
|
myPrefetchUrlList = new PrefetchUrlList();
|
||||||
|
}
|
||||||
|
|
||||||
|
int itemNo = 0;
|
||||||
|
if (!myPrefetchUrlList.stream()
|
||||||
|
.anyMatch(p -> p.equals("Patient/{{context.patientId}}")
|
||||||
|
|| p.equals("Patient?_id={{context.patientId}}")
|
||||||
|
|| p.equals("Patient?_id=Patient/{{context.patientId}}"))) {
|
||||||
|
String key = getKey(++itemNo);
|
||||||
|
service.addPrefetch(key, "Patient?_id={{context.patientId}}");
|
||||||
|
service.addSource(key, CdsResolutionStrategyEnum.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String item : myPrefetchUrlList) {
|
||||||
|
String key = getKey(++itemNo);
|
||||||
|
service.addPrefetch(key, item);
|
||||||
|
service.addSource(key, CdsResolutionStrategyEnum.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.CdsResolutionStrategyEnum;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
import org.hl7.fhir.r4.model.TriggerDefinition;
|
||||||
|
import org.hl7.fhir.r5.model.PlanDefinition;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CrDiscoveryElementR5 implements ICrDiscoveryElement {
|
||||||
|
protected PlanDefinition myPlanDefinition;
|
||||||
|
protected PrefetchUrlList myPrefetchUrlList;
|
||||||
|
|
||||||
|
public CrDiscoveryElementR5(PlanDefinition thePlanDefinition, PrefetchUrlList thePrefetchUrlList) {
|
||||||
|
myPlanDefinition = thePlanDefinition;
|
||||||
|
myPrefetchUrlList = thePrefetchUrlList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceJson getCdsServiceJson() {
|
||||||
|
if (myPlanDefinition == null
|
||||||
|
|| !myPlanDefinition.hasAction()
|
||||||
|
|| myPlanDefinition.getAction().stream().noneMatch(a -> a.hasTrigger())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var triggerDefs = myPlanDefinition.getAction().stream()
|
||||||
|
.filter(a -> a.hasTrigger())
|
||||||
|
.flatMap(a -> a.getTrigger().stream())
|
||||||
|
.filter(t -> t.getType().equals(TriggerDefinition.TriggerType.NAMEDEVENT))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (triggerDefs == null || triggerDefs.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var service = new CdsServiceJson()
|
||||||
|
.setId(myPlanDefinition.getIdElement().getIdPart())
|
||||||
|
.setTitle(myPlanDefinition.getTitle())
|
||||||
|
.setDescription(myPlanDefinition.getDescription())
|
||||||
|
.setHook(triggerDefs.get(0).getName());
|
||||||
|
|
||||||
|
if (myPrefetchUrlList == null) {
|
||||||
|
myPrefetchUrlList = new PrefetchUrlList();
|
||||||
|
}
|
||||||
|
|
||||||
|
int itemNo = 0;
|
||||||
|
if (!myPrefetchUrlList.stream()
|
||||||
|
.anyMatch(p -> p.equals("Patient/{{context.patientId}}")
|
||||||
|
|| p.equals("Patient?_id={{context.patientId}}")
|
||||||
|
|| p.equals("Patient?_id=Patient/{{context.patientId}}"))) {
|
||||||
|
String key = getKey(++itemNo);
|
||||||
|
service.addPrefetch(key, "Patient?_id={{context.patientId}}");
|
||||||
|
service.addSource(key, CdsResolutionStrategyEnum.SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String item : myPrefetchUrlList) {
|
||||||
|
String key = getKey(++itemNo);
|
||||||
|
service.addPrefetch(key, item);
|
||||||
|
service.addSource(key, CdsResolutionStrategyEnum.SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,445 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrUtils;
|
||||||
|
import org.hl7.fhir.dstu3.model.Coding;
|
||||||
|
import org.hl7.fhir.dstu3.model.DataRequirement;
|
||||||
|
import org.hl7.fhir.dstu3.model.Library;
|
||||||
|
import org.hl7.fhir.dstu3.model.PlanDefinition;
|
||||||
|
import org.hl7.fhir.dstu3.model.StringType;
|
||||||
|
import org.hl7.fhir.dstu3.model.ValueSet;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
import org.opencds.cqf.fhir.utility.dstu3.SearchHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CrDiscoveryServiceDstu3 implements ICrDiscoveryService {
|
||||||
|
|
||||||
|
protected final String PATIENT_ID_CONTEXT = "{{context.patientId}}";
|
||||||
|
protected final int DEFAULT_MAX_URI_LENGTH = 8000;
|
||||||
|
protected int myMaxUriLength;
|
||||||
|
|
||||||
|
protected Repository myRepository;
|
||||||
|
protected final IIdType myPlanDefinitionId;
|
||||||
|
|
||||||
|
public CrDiscoveryServiceDstu3(IIdType thePlanDefinitionId, Repository theRepository) {
|
||||||
|
myPlanDefinitionId = thePlanDefinitionId;
|
||||||
|
myRepository = theRepository;
|
||||||
|
myMaxUriLength = DEFAULT_MAX_URI_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceJson resolveService() {
|
||||||
|
return resolveService(
|
||||||
|
CdsCrUtils.readPlanDefinitionFromRepository(FhirVersionEnum.DSTU3, myRepository, myPlanDefinitionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceJson resolveService(IBaseResource thePlanDefinition) {
|
||||||
|
if (thePlanDefinition instanceof PlanDefinition) {
|
||||||
|
PlanDefinition planDef = (PlanDefinition) thePlanDefinition;
|
||||||
|
return new CrDiscoveryElementDstu3(planDef, getPrefetchUrlList(planDef)).getCdsServiceJson();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEca(PlanDefinition thePlanDefinition) {
|
||||||
|
if (thePlanDefinition.hasType() && thePlanDefinition.getType().hasCoding()) {
|
||||||
|
for (Coding coding : thePlanDefinition.getType().getCoding()) {
|
||||||
|
if (coding.getCode().equals("eca-rule")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||||
|
// Assuming 1 library
|
||||||
|
// TODO: enhance to handle multiple libraries - need a way to identify primary
|
||||||
|
// library
|
||||||
|
Library library = null;
|
||||||
|
if (thePlanDefinition.hasLibrary()
|
||||||
|
&& thePlanDefinition.getLibraryFirstRep().hasReference()) {
|
||||||
|
library = myRepository.read(
|
||||||
|
Library.class, thePlanDefinition.getLibraryFirstRep().getReferenceElement());
|
||||||
|
}
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveValueCodingCodes(List<Coding> theValueCodings) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
|
||||||
|
StringBuilder codes = new StringBuilder();
|
||||||
|
for (Coding coding : theValueCodings) {
|
||||||
|
if (coding.hasCode()) {
|
||||||
|
String system = coding.getSystem();
|
||||||
|
String code = coding.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(codes.toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveValueSetCodes(StringType theValueSetId) {
|
||||||
|
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, theValueSetId);
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
StringBuilder codes = new StringBuilder();
|
||||||
|
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||||
|
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||||
|
valueSet.getExpansion().getContains()) {
|
||||||
|
String system = contains.getSystem();
|
||||||
|
String code = contains.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||||
|
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||||
|
String system = concepts.getSystem();
|
||||||
|
if (concepts.hasConcept()) {
|
||||||
|
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||||
|
String code = concept.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(codes.toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StringBuilder getCodesStringBuilder(
|
||||||
|
List<String> theList, StringBuilder theCodes, String theSystem, String theCode) {
|
||||||
|
String codeToken = theSystem + "|" + theCode;
|
||||||
|
int postAppendLength = theCodes.length() + codeToken.length();
|
||||||
|
|
||||||
|
if (theCodes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||||
|
theCodes.append(",");
|
||||||
|
} else if (postAppendLength > myMaxUriLength) {
|
||||||
|
theList.add(theCodes.toString());
|
||||||
|
theCodes = new StringBuilder();
|
||||||
|
}
|
||||||
|
theCodes.append(codeToken);
|
||||||
|
return theCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||||
|
if (!isPatientCompartment(theDataRequirement.getType())) return null;
|
||||||
|
String patientRelatedResource = theDataRequirement.getType() + "?"
|
||||||
|
+ getPatientSearchParam(theDataRequirement.getType())
|
||||||
|
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||||
|
List<String> ret = new ArrayList<>();
|
||||||
|
if (theDataRequirement.hasCodeFilter()) {
|
||||||
|
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||||
|
theDataRequirement.getCodeFilter()) {
|
||||||
|
if (!codeFilterComponent.hasPath()) continue;
|
||||||
|
String path = mapCodePathToSearchParam(theDataRequirement.getType(), codeFilterComponent.getPath());
|
||||||
|
|
||||||
|
StringType codeFilterComponentString = null;
|
||||||
|
if (codeFilterComponent.hasValueSetStringType()) {
|
||||||
|
codeFilterComponentString = codeFilterComponent.getValueSetStringType();
|
||||||
|
} else if (codeFilterComponent.hasValueSetReference()) {
|
||||||
|
codeFilterComponentString = new StringType(
|
||||||
|
codeFilterComponent.getValueSetReference().getReference());
|
||||||
|
} else if (codeFilterComponent.hasValueCoding()) {
|
||||||
|
List<Coding> codeFilterValueCodings = codeFilterComponent.getValueCoding();
|
||||||
|
boolean isFirstCodingInFilter = true;
|
||||||
|
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||||
|
if (isFirstCodingInFilter) {
|
||||||
|
ret.add(patientRelatedResource + "&" + path + "=" + code);
|
||||||
|
} else {
|
||||||
|
ret.add("," + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirstCodingInFilter = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeFilterComponentString != null) {
|
||||||
|
for (String codes : resolveValueSetCodes(codeFilterComponentString)) {
|
||||||
|
ret.add(patientRelatedResource + "&" + path + "=" + codes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
ret.add(patientRelatedResource);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||||
|
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||||
|
if (thePlanDefinition == null) return null;
|
||||||
|
if (!isEca(thePlanDefinition)) return null;
|
||||||
|
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||||
|
// TODO: resolve data requirements
|
||||||
|
if (!library.hasDataRequirement()) return null;
|
||||||
|
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||||
|
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||||
|
if (requestUrls != null) {
|
||||||
|
prefetchList.addAll(requestUrls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefetchList;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||||
|
switch (theDataType) {
|
||||||
|
case "MedicationAdministration":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationDispense":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationRequest":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationStatement":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "ProcedureRequest":
|
||||||
|
if (thePath.equals("bodySite")) return "body-site";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return thePath.replace('.', '-').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPatientCompartment(String theDataType) {
|
||||||
|
if (theDataType == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (theDataType) {
|
||||||
|
case "Account":
|
||||||
|
case "AdverseEvent":
|
||||||
|
case "AllergyIntolerance":
|
||||||
|
case "Appointment":
|
||||||
|
case "AppointmentResponse":
|
||||||
|
case "AuditEvent":
|
||||||
|
case "Basic":
|
||||||
|
case "BodySite":
|
||||||
|
case "CarePlan":
|
||||||
|
case "CareTeam":
|
||||||
|
case "ChargeItem":
|
||||||
|
case "Claim":
|
||||||
|
case "ClaimResponse":
|
||||||
|
case "ClinicalImpression":
|
||||||
|
case "Communication":
|
||||||
|
case "CommunicationRequest":
|
||||||
|
case "Composition":
|
||||||
|
case "Condition":
|
||||||
|
case "Consent":
|
||||||
|
case "Coverage":
|
||||||
|
case "DetectedIssue":
|
||||||
|
case "DeviceRequest":
|
||||||
|
case "DeviceUseStatement":
|
||||||
|
case "DiagnosticReport":
|
||||||
|
case "DocumentManifest":
|
||||||
|
case "EligibilityRequest":
|
||||||
|
case "Encounter":
|
||||||
|
case "EnrollmentRequest":
|
||||||
|
case "EpisodeOfCare":
|
||||||
|
case "ExplanationOfBenefit":
|
||||||
|
case "FamilyMemberHistory":
|
||||||
|
case "Flag":
|
||||||
|
case "Goal":
|
||||||
|
case "Group":
|
||||||
|
case "ImagingManifest":
|
||||||
|
case "ImagingStudy":
|
||||||
|
case "Immunization":
|
||||||
|
case "ImmunizationRecommendation":
|
||||||
|
case "List":
|
||||||
|
case "MeasureReport":
|
||||||
|
case "Media":
|
||||||
|
case "MedicationAdministration":
|
||||||
|
case "MedicationDispense":
|
||||||
|
case "MedicationRequest":
|
||||||
|
case "MedicationStatement":
|
||||||
|
case "NutritionOrder":
|
||||||
|
case "Observation":
|
||||||
|
case "Patient":
|
||||||
|
case "Person":
|
||||||
|
case "Procedure":
|
||||||
|
case "ProcedureRequest":
|
||||||
|
case "Provenance":
|
||||||
|
case "QuestionnaireResponse":
|
||||||
|
case "ReferralRequest":
|
||||||
|
case "RelatedPerson":
|
||||||
|
case "RequestGroup":
|
||||||
|
case "ResearchSubject":
|
||||||
|
case "RiskAssessment":
|
||||||
|
case "Schedule":
|
||||||
|
case "Specimen":
|
||||||
|
case "SupplyDelivery":
|
||||||
|
case "SupplyRequest":
|
||||||
|
case "VisionPrescription":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPatientSearchParam(String theDataType) {
|
||||||
|
switch (theDataType) {
|
||||||
|
case "Account":
|
||||||
|
return "subject";
|
||||||
|
case "AdverseEvent":
|
||||||
|
return "subject";
|
||||||
|
case "AllergyIntolerance":
|
||||||
|
return "patient";
|
||||||
|
case "Appointment":
|
||||||
|
return "actor";
|
||||||
|
case "AppointmentResponse":
|
||||||
|
return "actor";
|
||||||
|
case "AuditEvent":
|
||||||
|
return "patient";
|
||||||
|
case "Basic":
|
||||||
|
return "patient";
|
||||||
|
case "BodySite":
|
||||||
|
return "patient";
|
||||||
|
case "CarePlan":
|
||||||
|
return "patient";
|
||||||
|
case "CareTeam":
|
||||||
|
return "patient";
|
||||||
|
case "ChargeItem":
|
||||||
|
return "subject";
|
||||||
|
case "Claim":
|
||||||
|
return "patient";
|
||||||
|
case "ClaimResponse":
|
||||||
|
return "patient";
|
||||||
|
case "ClinicalImpression":
|
||||||
|
return "subject";
|
||||||
|
case "Communication":
|
||||||
|
return "subject";
|
||||||
|
case "CommunicationRequest":
|
||||||
|
return "subject";
|
||||||
|
case "Composition":
|
||||||
|
return "subject";
|
||||||
|
case "Condition":
|
||||||
|
return "patient";
|
||||||
|
case "Consent":
|
||||||
|
return "patient";
|
||||||
|
case "Coverage":
|
||||||
|
return "patient";
|
||||||
|
case "DetectedIssue":
|
||||||
|
return "patient";
|
||||||
|
case "DeviceRequest":
|
||||||
|
return "subject";
|
||||||
|
case "DeviceUseStatement":
|
||||||
|
return "subject";
|
||||||
|
case "DiagnosticReport":
|
||||||
|
return "subject";
|
||||||
|
case "DocumentManifest":
|
||||||
|
return "subject";
|
||||||
|
case "DocumentReference":
|
||||||
|
return "subject";
|
||||||
|
case "EligibilityRequest":
|
||||||
|
return "patient";
|
||||||
|
case "Encounter":
|
||||||
|
return "patient";
|
||||||
|
case "EnrollmentRequest":
|
||||||
|
return "subject";
|
||||||
|
case "EpisodeOfCare":
|
||||||
|
return "patient";
|
||||||
|
case "ExplanationOfBenefit":
|
||||||
|
return "patient";
|
||||||
|
case "FamilyMemberHistory":
|
||||||
|
return "patient";
|
||||||
|
case "Flag":
|
||||||
|
return "patient";
|
||||||
|
case "Goal":
|
||||||
|
return "patient";
|
||||||
|
case "Group":
|
||||||
|
return "member";
|
||||||
|
case "ImagingManifest":
|
||||||
|
return "patient";
|
||||||
|
case "ImagingStudy":
|
||||||
|
return "patient";
|
||||||
|
case "Immunization":
|
||||||
|
return "patient";
|
||||||
|
case "ImmunizationRecommendation":
|
||||||
|
return "patient";
|
||||||
|
case "List":
|
||||||
|
return "subject";
|
||||||
|
case "MeasureReport":
|
||||||
|
return "patient";
|
||||||
|
case "Media":
|
||||||
|
return "subject";
|
||||||
|
case "MedicationAdministration":
|
||||||
|
return "patient";
|
||||||
|
case "MedicationDispense":
|
||||||
|
return "patient";
|
||||||
|
case "MedicationRequest":
|
||||||
|
return "subject";
|
||||||
|
case "MedicationStatement":
|
||||||
|
return "subject";
|
||||||
|
case "NutritionOrder":
|
||||||
|
return "patient";
|
||||||
|
case "Observation":
|
||||||
|
return "subject";
|
||||||
|
case "Patient":
|
||||||
|
return "_id";
|
||||||
|
case "Person":
|
||||||
|
return "patient";
|
||||||
|
case "Procedure":
|
||||||
|
return "patient";
|
||||||
|
case "ProcedureRequest":
|
||||||
|
return "patient";
|
||||||
|
case "Provenance":
|
||||||
|
return "patient";
|
||||||
|
case "QuestionnaireResponse":
|
||||||
|
return "subject";
|
||||||
|
case "ReferralRequest":
|
||||||
|
return "patient";
|
||||||
|
case "RelatedPerson":
|
||||||
|
return "patient";
|
||||||
|
case "RequestGroup":
|
||||||
|
return "subject";
|
||||||
|
case "ResearchSubject":
|
||||||
|
return "individual";
|
||||||
|
case "RiskAssessment":
|
||||||
|
return "subject";
|
||||||
|
case "Schedule":
|
||||||
|
return "actor";
|
||||||
|
case "Specimen":
|
||||||
|
return "subject";
|
||||||
|
case "SupplyDelivery":
|
||||||
|
return "patient";
|
||||||
|
case "SupplyRequest":
|
||||||
|
return "subject";
|
||||||
|
case "VisionPrescription":
|
||||||
|
return "patient";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,429 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrUtils;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r4.model.CanonicalType;
|
||||||
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
|
import org.hl7.fhir.r4.model.DataRequirement;
|
||||||
|
import org.hl7.fhir.r4.model.Library;
|
||||||
|
import org.hl7.fhir.r4.model.PlanDefinition;
|
||||||
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
import org.opencds.cqf.fhir.utility.r4.SearchHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CrDiscoveryServiceR4 implements ICrDiscoveryService {
|
||||||
|
|
||||||
|
protected final String PATIENT_ID_CONTEXT = "{{context.patientId}}";
|
||||||
|
protected final int DEFAULT_MAX_URI_LENGTH = 8000;
|
||||||
|
protected int myMaxUriLength;
|
||||||
|
|
||||||
|
protected final Repository myRepository;
|
||||||
|
protected final IIdType myPlanDefinitionId;
|
||||||
|
|
||||||
|
public CrDiscoveryServiceR4(IIdType thePlanDefinitionId, Repository theRepository) {
|
||||||
|
myPlanDefinitionId = thePlanDefinitionId;
|
||||||
|
myRepository = theRepository;
|
||||||
|
myMaxUriLength = DEFAULT_MAX_URI_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceJson resolveService() {
|
||||||
|
return resolveService(
|
||||||
|
CdsCrUtils.readPlanDefinitionFromRepository(FhirVersionEnum.R4, myRepository, myPlanDefinitionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceJson resolveService(IBaseResource thePlanDefinition) {
|
||||||
|
if (thePlanDefinition instanceof PlanDefinition) {
|
||||||
|
PlanDefinition planDef = (PlanDefinition) thePlanDefinition;
|
||||||
|
return new CrDiscoveryElementR4(planDef, getPrefetchUrlList(planDef)).getCdsServiceJson();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEca(PlanDefinition planDefinition) {
|
||||||
|
if (planDefinition.hasType() && planDefinition.getType().hasCoding()) {
|
||||||
|
for (Coding coding : planDefinition.getType().getCoding()) {
|
||||||
|
if (coding.getCode().equals("eca-rule")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||||
|
// The CPGComputablePlanDefinition profile limits the cardinality of library to 1
|
||||||
|
Library library = null;
|
||||||
|
if (thePlanDefinition.hasLibrary() && !thePlanDefinition.getLibrary().isEmpty()) {
|
||||||
|
library = (Library) SearchHelper.searchRepositoryByCanonical(
|
||||||
|
myRepository, thePlanDefinition.getLibrary().get(0));
|
||||||
|
}
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveValueCodingCodes(List<Coding> valueCodings) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
|
||||||
|
StringBuilder codes = new StringBuilder();
|
||||||
|
for (Coding coding : valueCodings) {
|
||||||
|
if (coding.hasCode()) {
|
||||||
|
String system = coding.getSystem();
|
||||||
|
String code = coding.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(codes.toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveValueSetCodes(CanonicalType valueSetId) {
|
||||||
|
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, valueSetId);
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
StringBuilder codes = new StringBuilder();
|
||||||
|
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||||
|
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||||
|
valueSet.getExpansion().getContains()) {
|
||||||
|
String system = contains.getSystem();
|
||||||
|
String code = contains.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||||
|
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||||
|
String system = concepts.getSystem();
|
||||||
|
if (concepts.hasConcept()) {
|
||||||
|
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||||
|
String code = concept.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(codes.toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StringBuilder getCodesStringBuilder(List<String> ret, StringBuilder codes, String system, String code) {
|
||||||
|
String codeToken = system + "|" + code;
|
||||||
|
int postAppendLength = codes.length() + codeToken.length();
|
||||||
|
|
||||||
|
if (codes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||||
|
codes.append(",");
|
||||||
|
} else if (postAppendLength > myMaxUriLength) {
|
||||||
|
ret.add(codes.toString());
|
||||||
|
codes = new StringBuilder();
|
||||||
|
}
|
||||||
|
codes.append(codeToken);
|
||||||
|
return codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||||
|
if (!isPatientCompartment(theDataRequirement.getType())) return null;
|
||||||
|
String patientRelatedResource = theDataRequirement.getType() + "?"
|
||||||
|
+ getPatientSearchParam(theDataRequirement.getType())
|
||||||
|
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||||
|
List<String> ret = new ArrayList<>();
|
||||||
|
if (theDataRequirement.hasCodeFilter()) {
|
||||||
|
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||||
|
theDataRequirement.getCodeFilter()) {
|
||||||
|
if (!codeFilterComponent.hasPath()) continue;
|
||||||
|
String path = mapCodePathToSearchParam(theDataRequirement.getType(), codeFilterComponent.getPath());
|
||||||
|
if (codeFilterComponent.hasValueSetElement()) {
|
||||||
|
for (String codes : resolveValueSetCodes(codeFilterComponent.getValueSetElement())) {
|
||||||
|
ret.add(patientRelatedResource + "&" + path + "=" + codes);
|
||||||
|
}
|
||||||
|
} else if (codeFilterComponent.hasCode()) {
|
||||||
|
List<Coding> codeFilterValueCodings = codeFilterComponent.getCode();
|
||||||
|
boolean isFirstCodingInFilter = true;
|
||||||
|
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||||
|
if (isFirstCodingInFilter) {
|
||||||
|
ret.add(patientRelatedResource + "&" + path + "=" + code);
|
||||||
|
} else {
|
||||||
|
ret.add("," + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirstCodingInFilter = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
ret.add(patientRelatedResource);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||||
|
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||||
|
if (thePlanDefinition == null) return null;
|
||||||
|
if (!isEca(thePlanDefinition)) return null;
|
||||||
|
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||||
|
// TODO: resolve data requirements
|
||||||
|
if (library == null || !library.hasDataRequirement()) return null;
|
||||||
|
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||||
|
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||||
|
if (requestUrls != null) {
|
||||||
|
prefetchList.addAll(requestUrls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prefetchList;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||||
|
switch (theDataType) {
|
||||||
|
case "MedicationAdministration":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationDispense":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationRequest":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationStatement":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return thePath.replace('.', '-').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPatientCompartment(String theDataType) {
|
||||||
|
if (theDataType == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (theDataType) {
|
||||||
|
case "Account":
|
||||||
|
case "AdverseEvent":
|
||||||
|
case "AllergyIntolerance":
|
||||||
|
case "Appointment":
|
||||||
|
case "AppointmentResponse":
|
||||||
|
case "AuditEvent":
|
||||||
|
case "Basic":
|
||||||
|
case "BodyStructure":
|
||||||
|
case "CarePlan":
|
||||||
|
case "CareTeam":
|
||||||
|
case "ChargeItem":
|
||||||
|
case "Claim":
|
||||||
|
case "ClaimResponse":
|
||||||
|
case "ClinicalImpression":
|
||||||
|
case "Communication":
|
||||||
|
case "CommunicationRequest":
|
||||||
|
case "Composition":
|
||||||
|
case "Condition":
|
||||||
|
case "Consent":
|
||||||
|
case "Coverage":
|
||||||
|
case "CoverageEligibilityRequest":
|
||||||
|
case "CoverageEligibilityResponse":
|
||||||
|
case "DetectedIssue":
|
||||||
|
case "DeviceRequest":
|
||||||
|
case "DeviceUseStatement":
|
||||||
|
case "DiagnosticReport":
|
||||||
|
case "DocumentManifest":
|
||||||
|
case "DocumentReference":
|
||||||
|
case "Encounter":
|
||||||
|
case "EnrollmentRequest":
|
||||||
|
case "EpisodeOfCare":
|
||||||
|
case "ExplanationOfBenefit":
|
||||||
|
case "FamilyMemberHistory":
|
||||||
|
case "Flag":
|
||||||
|
case "Goal":
|
||||||
|
case "Group":
|
||||||
|
case "ImagingStudy":
|
||||||
|
case "Immunization":
|
||||||
|
case "ImmunizationEvaluation":
|
||||||
|
case "ImmunizationRecommendation":
|
||||||
|
case "Invoice":
|
||||||
|
case "List":
|
||||||
|
case "MeasureReport":
|
||||||
|
case "Media":
|
||||||
|
case "MedicationAdministration":
|
||||||
|
case "MedicationDispense":
|
||||||
|
case "MedicationRequest":
|
||||||
|
case "MedicationStatement":
|
||||||
|
case "MolecularSequence":
|
||||||
|
case "NutritionOrder":
|
||||||
|
case "Observation":
|
||||||
|
case "Patient":
|
||||||
|
case "Person":
|
||||||
|
case "Procedure":
|
||||||
|
case "Provenance":
|
||||||
|
case "QuestionnaireResponse":
|
||||||
|
case "RelatedPerson":
|
||||||
|
case "RequestGroup":
|
||||||
|
case "ResearchSubject":
|
||||||
|
case "RiskAssessment":
|
||||||
|
case "Schedule":
|
||||||
|
case "ServiceRequest":
|
||||||
|
case "Specimen":
|
||||||
|
case "SupplyDelivery":
|
||||||
|
case "SupplyRequest":
|
||||||
|
case "VisionPrescription":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPatientSearchParam(String theDataType) {
|
||||||
|
switch (theDataType) {
|
||||||
|
case "Account":
|
||||||
|
return "subject";
|
||||||
|
case "AdverseEvent":
|
||||||
|
return "subject";
|
||||||
|
case "AllergyIntolerance":
|
||||||
|
return "patient";
|
||||||
|
case "Appointment":
|
||||||
|
return "actor";
|
||||||
|
case "AppointmentResponse":
|
||||||
|
return "actor";
|
||||||
|
case "AuditEvent":
|
||||||
|
return "patient";
|
||||||
|
case "Basic":
|
||||||
|
return "patient";
|
||||||
|
case "BodyStructure":
|
||||||
|
return "patient";
|
||||||
|
case "CarePlan":
|
||||||
|
return "patient";
|
||||||
|
case "CareTeam":
|
||||||
|
return "patient";
|
||||||
|
case "ChargeItem":
|
||||||
|
return "subject";
|
||||||
|
case "Claim":
|
||||||
|
return "patient";
|
||||||
|
case "ClaimResponse":
|
||||||
|
return "patient";
|
||||||
|
case "ClinicalImpression":
|
||||||
|
return "subject";
|
||||||
|
case "Communication":
|
||||||
|
return "subject";
|
||||||
|
case "CommunicationRequest":
|
||||||
|
return "subject";
|
||||||
|
case "Composition":
|
||||||
|
return "subject";
|
||||||
|
case "Condition":
|
||||||
|
return "patient";
|
||||||
|
case "Consent":
|
||||||
|
return "patient";
|
||||||
|
case "Coverage":
|
||||||
|
return "policy-holder";
|
||||||
|
case "DetectedIssue":
|
||||||
|
return "patient";
|
||||||
|
case "DeviceRequest":
|
||||||
|
return "subject";
|
||||||
|
case "DeviceUseStatement":
|
||||||
|
return "subject";
|
||||||
|
case "DiagnosticReport":
|
||||||
|
return "subject";
|
||||||
|
case "DocumentManifest":
|
||||||
|
return "subject";
|
||||||
|
case "DocumentReference":
|
||||||
|
return "subject";
|
||||||
|
case "Encounter":
|
||||||
|
return "patient";
|
||||||
|
case "EnrollmentRequest":
|
||||||
|
return "subject";
|
||||||
|
case "EpisodeOfCare":
|
||||||
|
return "patient";
|
||||||
|
case "ExplanationOfBenefit":
|
||||||
|
return "patient";
|
||||||
|
case "FamilyMemberHistory":
|
||||||
|
return "patient";
|
||||||
|
case "Flag":
|
||||||
|
return "patient";
|
||||||
|
case "Goal":
|
||||||
|
return "patient";
|
||||||
|
case "Group":
|
||||||
|
return "member";
|
||||||
|
case "ImagingStudy":
|
||||||
|
return "patient";
|
||||||
|
case "Immunization":
|
||||||
|
return "patient";
|
||||||
|
case "ImmunizationRecommendation":
|
||||||
|
return "patient";
|
||||||
|
case "Invoice":
|
||||||
|
return "subject";
|
||||||
|
case "List":
|
||||||
|
return "subject";
|
||||||
|
case "MeasureReport":
|
||||||
|
return "patient";
|
||||||
|
case "Media":
|
||||||
|
return "subject";
|
||||||
|
case "MedicationAdministration":
|
||||||
|
return "patient";
|
||||||
|
case "MedicationDispense":
|
||||||
|
return "patient";
|
||||||
|
case "MedicationRequest":
|
||||||
|
return "subject";
|
||||||
|
case "MedicationStatement":
|
||||||
|
return "subject";
|
||||||
|
case "MolecularSequence":
|
||||||
|
return "patient";
|
||||||
|
case "NutritionOrder":
|
||||||
|
return "patient";
|
||||||
|
case "Observation":
|
||||||
|
return "subject";
|
||||||
|
case "Patient":
|
||||||
|
return "_id";
|
||||||
|
case "Person":
|
||||||
|
return "patient";
|
||||||
|
case "Procedure":
|
||||||
|
return "patient";
|
||||||
|
case "Provenance":
|
||||||
|
return "patient";
|
||||||
|
case "QuestionnaireResponse":
|
||||||
|
return "subject";
|
||||||
|
case "RelatedPerson":
|
||||||
|
return "patient";
|
||||||
|
case "RequestGroup":
|
||||||
|
return "subject";
|
||||||
|
case "ResearchSubject":
|
||||||
|
return "individual";
|
||||||
|
case "RiskAssessment":
|
||||||
|
return "subject";
|
||||||
|
case "Schedule":
|
||||||
|
return "actor";
|
||||||
|
case "ServiceRequest":
|
||||||
|
return "patient";
|
||||||
|
case "Specimen":
|
||||||
|
return "subject";
|
||||||
|
case "SupplyDelivery":
|
||||||
|
return "patient";
|
||||||
|
case "SupplyRequest":
|
||||||
|
return "subject";
|
||||||
|
case "VisionPrescription":
|
||||||
|
return "patient";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,431 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrUtils;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r5.model.CanonicalType;
|
||||||
|
import org.hl7.fhir.r5.model.Coding;
|
||||||
|
import org.hl7.fhir.r5.model.DataRequirement;
|
||||||
|
import org.hl7.fhir.r5.model.Library;
|
||||||
|
import org.hl7.fhir.r5.model.PlanDefinition;
|
||||||
|
import org.hl7.fhir.r5.model.ValueSet;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
import org.opencds.cqf.fhir.utility.r5.SearchHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CrDiscoveryServiceR5 implements ICrDiscoveryService {
|
||||||
|
|
||||||
|
protected final String PATIENT_ID_CONTEXT = "{{context.patientId}}";
|
||||||
|
protected final int DEFAULT_MAX_URI_LENGTH = 8000;
|
||||||
|
protected int myMaxUriLength;
|
||||||
|
|
||||||
|
protected final Repository myRepository;
|
||||||
|
protected final IIdType myPlanDefinitionId;
|
||||||
|
|
||||||
|
public CrDiscoveryServiceR5(IIdType thePlanDefinitionId, Repository theRepository) {
|
||||||
|
myPlanDefinitionId = thePlanDefinitionId;
|
||||||
|
myRepository = theRepository;
|
||||||
|
myMaxUriLength = DEFAULT_MAX_URI_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsServiceJson resolveService() {
|
||||||
|
return resolveService(
|
||||||
|
CdsCrUtils.readPlanDefinitionFromRepository(FhirVersionEnum.R5, myRepository, myPlanDefinitionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CdsServiceJson resolveService(IBaseResource thePlanDefinition) {
|
||||||
|
if (thePlanDefinition instanceof PlanDefinition) {
|
||||||
|
PlanDefinition planDef = (PlanDefinition) thePlanDefinition;
|
||||||
|
return new CrDiscoveryElementR5(planDef, getPrefetchUrlList(planDef)).getCdsServiceJson();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEca(PlanDefinition thePlanDefinition) {
|
||||||
|
if (thePlanDefinition.hasType() && thePlanDefinition.getType().hasCoding()) {
|
||||||
|
for (Coding coding : thePlanDefinition.getType().getCoding()) {
|
||||||
|
if (coding.getCode().equals("eca-rule")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||||
|
// The CPGComputablePlanDefinition profile limits the cardinality of library to 1
|
||||||
|
Library library = null;
|
||||||
|
if (thePlanDefinition.hasLibrary() && !thePlanDefinition.getLibrary().isEmpty()) {
|
||||||
|
library = (Library) SearchHelper.searchRepositoryByCanonical(
|
||||||
|
myRepository, thePlanDefinition.getLibrary().get(0));
|
||||||
|
}
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveValueCodingCodes(List<Coding> theValueCodings) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
|
||||||
|
StringBuilder codes = new StringBuilder();
|
||||||
|
for (Coding coding : theValueCodings) {
|
||||||
|
if (coding.hasCode()) {
|
||||||
|
String system = coding.getSystem();
|
||||||
|
String code = coding.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(codes.toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveValueSetCodes(CanonicalType theValueSetId) {
|
||||||
|
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, theValueSetId);
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
StringBuilder codes = new StringBuilder();
|
||||||
|
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||||
|
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||||
|
valueSet.getExpansion().getContains()) {
|
||||||
|
String system = contains.getSystem();
|
||||||
|
String code = contains.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||||
|
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||||
|
String system = concepts.getSystem();
|
||||||
|
if (concepts.hasConcept()) {
|
||||||
|
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||||
|
String code = concept.getCode();
|
||||||
|
|
||||||
|
codes = getCodesStringBuilder(result, codes, system, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(codes.toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StringBuilder getCodesStringBuilder(
|
||||||
|
List<String> theList, StringBuilder theCodes, String theSystem, String theCode) {
|
||||||
|
String codeToken = theSystem + "|" + theCode;
|
||||||
|
int postAppendLength = theCodes.length() + codeToken.length();
|
||||||
|
|
||||||
|
if (theCodes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||||
|
theCodes.append(",");
|
||||||
|
} else if (postAppendLength > myMaxUriLength) {
|
||||||
|
theList.add(theCodes.toString());
|
||||||
|
theCodes = new StringBuilder();
|
||||||
|
}
|
||||||
|
theCodes.append(codeToken);
|
||||||
|
return theCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||||
|
if (!isPatientCompartment(theDataRequirement.getType().toCode())) return null;
|
||||||
|
String patientRelatedResource = theDataRequirement.getType() + "?"
|
||||||
|
+ getPatientSearchParam(theDataRequirement.getType().toCode())
|
||||||
|
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||||
|
List<String> ret = new ArrayList<>();
|
||||||
|
if (theDataRequirement.hasCodeFilter()) {
|
||||||
|
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||||
|
theDataRequirement.getCodeFilter()) {
|
||||||
|
if (!codeFilterComponent.hasPath()) continue;
|
||||||
|
String path =
|
||||||
|
mapCodePathToSearchParam(theDataRequirement.getType().toCode(), codeFilterComponent.getPath());
|
||||||
|
if (codeFilterComponent.hasValueSetElement()) {
|
||||||
|
for (String codes : resolveValueSetCodes(codeFilterComponent.getValueSetElement())) {
|
||||||
|
ret.add(patientRelatedResource + "&" + path + "=" + codes);
|
||||||
|
}
|
||||||
|
} else if (codeFilterComponent.hasCode()) {
|
||||||
|
List<Coding> codeFilterValueCodings = codeFilterComponent.getCode();
|
||||||
|
boolean isFirstCodingInFilter = true;
|
||||||
|
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||||
|
if (isFirstCodingInFilter) {
|
||||||
|
ret.add(patientRelatedResource + "&" + path + "=" + code);
|
||||||
|
} else {
|
||||||
|
ret.add("," + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirstCodingInFilter = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
ret.add(patientRelatedResource);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||||
|
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||||
|
if (thePlanDefinition == null) return null;
|
||||||
|
if (!isEca(thePlanDefinition)) return null;
|
||||||
|
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||||
|
// TODO: resolve data requirements
|
||||||
|
if (library == null || !library.hasDataRequirement()) return null;
|
||||||
|
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||||
|
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||||
|
if (requestUrls != null) {
|
||||||
|
prefetchList.addAll(requestUrls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prefetchList;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||||
|
switch (theDataType) {
|
||||||
|
case "MedicationAdministration":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationDispense":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationRequest":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
case "MedicationStatement":
|
||||||
|
if (thePath.equals("medication")) return "code";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return thePath.replace('.', '-').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPatientCompartment(String theDataType) {
|
||||||
|
if (theDataType == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (theDataType) {
|
||||||
|
case "Account":
|
||||||
|
case "AdverseEvent":
|
||||||
|
case "AllergyIntolerance":
|
||||||
|
case "Appointment":
|
||||||
|
case "AppointmentResponse":
|
||||||
|
case "AuditEvent":
|
||||||
|
case "Basic":
|
||||||
|
case "BodyStructure":
|
||||||
|
case "CarePlan":
|
||||||
|
case "CareTeam":
|
||||||
|
case "ChargeItem":
|
||||||
|
case "Claim":
|
||||||
|
case "ClaimResponse":
|
||||||
|
case "ClinicalImpression":
|
||||||
|
case "Communication":
|
||||||
|
case "CommunicationRequest":
|
||||||
|
case "Composition":
|
||||||
|
case "Condition":
|
||||||
|
case "Consent":
|
||||||
|
case "Coverage":
|
||||||
|
case "CoverageEligibilityRequest":
|
||||||
|
case "CoverageEligibilityResponse":
|
||||||
|
case "DetectedIssue":
|
||||||
|
case "DeviceRequest":
|
||||||
|
case "DeviceUseStatement":
|
||||||
|
case "DiagnosticReport":
|
||||||
|
case "DocumentManifest":
|
||||||
|
case "DocumentReference":
|
||||||
|
case "Encounter":
|
||||||
|
case "EnrollmentRequest":
|
||||||
|
case "EpisodeOfCare":
|
||||||
|
case "ExplanationOfBenefit":
|
||||||
|
case "FamilyMemberHistory":
|
||||||
|
case "Flag":
|
||||||
|
case "Goal":
|
||||||
|
case "Group":
|
||||||
|
case "ImagingStudy":
|
||||||
|
case "Immunization":
|
||||||
|
case "ImmunizationEvaluation":
|
||||||
|
case "ImmunizationRecommendation":
|
||||||
|
case "Invoice":
|
||||||
|
case "List":
|
||||||
|
case "MeasureReport":
|
||||||
|
case "Media":
|
||||||
|
case "MedicationAdministration":
|
||||||
|
case "MedicationDispense":
|
||||||
|
case "MedicationRequest":
|
||||||
|
case "MedicationStatement":
|
||||||
|
case "MolecularSequence":
|
||||||
|
case "NutritionOrder":
|
||||||
|
case "Observation":
|
||||||
|
case "Patient":
|
||||||
|
case "Person":
|
||||||
|
case "Procedure":
|
||||||
|
case "Provenance":
|
||||||
|
case "QuestionnaireResponse":
|
||||||
|
case "RelatedPerson":
|
||||||
|
case "RequestGroup":
|
||||||
|
case "ResearchSubject":
|
||||||
|
case "RiskAssessment":
|
||||||
|
case "Schedule":
|
||||||
|
case "ServiceRequest":
|
||||||
|
case "Specimen":
|
||||||
|
case "SupplyDelivery":
|
||||||
|
case "SupplyRequest":
|
||||||
|
case "VisionPrescription":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPatientSearchParam(String theDataType) {
|
||||||
|
switch (theDataType) {
|
||||||
|
case "Account":
|
||||||
|
return "subject";
|
||||||
|
case "AdverseEvent":
|
||||||
|
return "subject";
|
||||||
|
case "AllergyIntolerance":
|
||||||
|
return "patient";
|
||||||
|
case "Appointment":
|
||||||
|
return "actor";
|
||||||
|
case "AppointmentResponse":
|
||||||
|
return "actor";
|
||||||
|
case "AuditEvent":
|
||||||
|
return "patient";
|
||||||
|
case "Basic":
|
||||||
|
return "patient";
|
||||||
|
case "BodyStructure":
|
||||||
|
return "patient";
|
||||||
|
case "CarePlan":
|
||||||
|
return "patient";
|
||||||
|
case "CareTeam":
|
||||||
|
return "patient";
|
||||||
|
case "ChargeItem":
|
||||||
|
return "subject";
|
||||||
|
case "Claim":
|
||||||
|
return "patient";
|
||||||
|
case "ClaimResponse":
|
||||||
|
return "patient";
|
||||||
|
case "ClinicalImpression":
|
||||||
|
return "subject";
|
||||||
|
case "Communication":
|
||||||
|
return "subject";
|
||||||
|
case "CommunicationRequest":
|
||||||
|
return "subject";
|
||||||
|
case "Composition":
|
||||||
|
return "subject";
|
||||||
|
case "Condition":
|
||||||
|
return "patient";
|
||||||
|
case "Consent":
|
||||||
|
return "patient";
|
||||||
|
case "Coverage":
|
||||||
|
return "policy-holder";
|
||||||
|
case "DetectedIssue":
|
||||||
|
return "patient";
|
||||||
|
case "DeviceRequest":
|
||||||
|
return "subject";
|
||||||
|
case "DeviceUseStatement":
|
||||||
|
return "subject";
|
||||||
|
case "DiagnosticReport":
|
||||||
|
return "subject";
|
||||||
|
case "DocumentManifest":
|
||||||
|
return "subject";
|
||||||
|
case "DocumentReference":
|
||||||
|
return "subject";
|
||||||
|
case "Encounter":
|
||||||
|
return "patient";
|
||||||
|
case "EnrollmentRequest":
|
||||||
|
return "subject";
|
||||||
|
case "EpisodeOfCare":
|
||||||
|
return "patient";
|
||||||
|
case "ExplanationOfBenefit":
|
||||||
|
return "patient";
|
||||||
|
case "FamilyMemberHistory":
|
||||||
|
return "patient";
|
||||||
|
case "Flag":
|
||||||
|
return "patient";
|
||||||
|
case "Goal":
|
||||||
|
return "patient";
|
||||||
|
case "Group":
|
||||||
|
return "member";
|
||||||
|
case "ImagingStudy":
|
||||||
|
return "patient";
|
||||||
|
case "Immunization":
|
||||||
|
return "patient";
|
||||||
|
case "ImmunizationRecommendation":
|
||||||
|
return "patient";
|
||||||
|
case "Invoice":
|
||||||
|
return "subject";
|
||||||
|
case "List":
|
||||||
|
return "subject";
|
||||||
|
case "MeasureReport":
|
||||||
|
return "patient";
|
||||||
|
case "Media":
|
||||||
|
return "subject";
|
||||||
|
case "MedicationAdministration":
|
||||||
|
return "patient";
|
||||||
|
case "MedicationDispense":
|
||||||
|
return "patient";
|
||||||
|
case "MedicationRequest":
|
||||||
|
return "subject";
|
||||||
|
case "MedicationStatement":
|
||||||
|
return "subject";
|
||||||
|
case "MolecularSequence":
|
||||||
|
return "patient";
|
||||||
|
case "NutritionOrder":
|
||||||
|
return "patient";
|
||||||
|
case "Observation":
|
||||||
|
return "subject";
|
||||||
|
case "Patient":
|
||||||
|
return "_id";
|
||||||
|
case "Person":
|
||||||
|
return "patient";
|
||||||
|
case "Procedure":
|
||||||
|
return "patient";
|
||||||
|
case "Provenance":
|
||||||
|
return "patient";
|
||||||
|
case "QuestionnaireResponse":
|
||||||
|
return "subject";
|
||||||
|
case "RelatedPerson":
|
||||||
|
return "patient";
|
||||||
|
case "RequestGroup":
|
||||||
|
return "subject";
|
||||||
|
case "ResearchSubject":
|
||||||
|
return "individual";
|
||||||
|
case "RiskAssessment":
|
||||||
|
return "subject";
|
||||||
|
case "Schedule":
|
||||||
|
return "actor";
|
||||||
|
case "ServiceRequest":
|
||||||
|
return "patient";
|
||||||
|
case "Specimen":
|
||||||
|
return "subject";
|
||||||
|
case "SupplyDelivery":
|
||||||
|
return "patient";
|
||||||
|
case "SupplyRequest":
|
||||||
|
return "subject";
|
||||||
|
case "VisionPrescription":
|
||||||
|
return "patient";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
|
||||||
|
public interface ICrDiscoveryElement {
|
||||||
|
CdsServiceJson getCdsServiceJson();
|
||||||
|
|
||||||
|
default String getKey(int itemNo) {
|
||||||
|
return "item" + Integer.toString(itemNo);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
|
||||||
|
public interface ICrDiscoveryService {
|
||||||
|
CdsServiceJson resolveService();
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
public interface ICrDiscoveryServiceFactory {
|
||||||
|
ICrDiscoveryService create(String theServiceId);
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - CDS Hooks
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
public class PrefetchUrlList extends CopyOnWriteArrayList<String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(String theElement) {
|
||||||
|
for (String s : this) {
|
||||||
|
if (s.equals(theElement)) return false;
|
||||||
|
if (theElement.startsWith(s)) return false;
|
||||||
|
}
|
||||||
|
return super.add(theElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends String> theAdd) {
|
||||||
|
if (theAdd != null) {
|
||||||
|
for (String s : theAdd) {
|
||||||
|
add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import ca.uhn.hapi.fhir.cdshooks.api.ICdsHooksDaoAuthorizationSvc;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceMethod;
|
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceMethod;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.CdsCrServiceMethod;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -56,6 +57,11 @@ public class CdsPrefetchSvc {
|
||||||
|
|
||||||
public void augmentRequest(CdsServiceRequestJson theCdsServiceRequestJson, ICdsServiceMethod theServiceMethod) {
|
public void augmentRequest(CdsServiceRequestJson theCdsServiceRequestJson, ICdsServiceMethod theServiceMethod) {
|
||||||
CdsServiceJson serviceSpec = theServiceMethod.getCdsServiceJson();
|
CdsServiceJson serviceSpec = theServiceMethod.getCdsServiceJson();
|
||||||
|
if (theServiceMethod instanceof CdsCrServiceMethod) {
|
||||||
|
// CdsCrServices will retrieve data from the dao or fhir server passed in as needed,
|
||||||
|
// checking for missing prefetch is not necessary.
|
||||||
|
return;
|
||||||
|
}
|
||||||
Set<String> missingPrefetch = findMissingPrefetch(serviceSpec, theCdsServiceRequestJson);
|
Set<String> missingPrefetch = findMissingPrefetch(serviceSpec, theCdsServiceRequestJson);
|
||||||
if (missingPrefetch.isEmpty()) {
|
if (missingPrefetch.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration(classes = {TestCrConfig.class})
|
||||||
|
public abstract class BaseCrTest {
|
||||||
|
public static final String PLAN_DEFINITION_RESOURCE_NAME = "PlanDefinition";
|
||||||
|
protected static final String TEST_ADDRESS = "http://test:8000/fhir";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected FhirContext myFhirContext;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class TestCrConfig {
|
||||||
|
@Bean
|
||||||
|
FhirContext fhirContext() {
|
||||||
|
return FhirContext.forR4Cached();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.util.ClasspathUtil;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.module.CdsHooksObjectMapperFactory;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.BaseCrTest;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class CrDiscoveryServiceR4Test extends BaseCrTest {
|
||||||
|
@Test
|
||||||
|
public void testR4DiscoveryService() throws JsonProcessingException {
|
||||||
|
Bundle bundle = ClasspathUtil.loadResource(myFhirContext, Bundle.class, "Bundle-ASLPCrd-Content.json");
|
||||||
|
Repository repository = new InMemoryFhirRepository(myFhirContext, bundle);
|
||||||
|
|
||||||
|
final IdType planDefinitionId = new IdType(PLAN_DEFINITION_RESOURCE_NAME, "ASLPCrd");
|
||||||
|
final CdsServiceJson cdsServiceJson = new CrDiscoveryServiceR4(planDefinitionId, repository).resolveService();
|
||||||
|
final ObjectMapper objectMapper = new CdsHooksObjectMapperFactory(myFhirContext).newMapper();
|
||||||
|
// execute
|
||||||
|
final String actual = objectMapper.writeValueAsString(cdsServiceJson);
|
||||||
|
final String expected = "{\n" +
|
||||||
|
" \"hook\" : \"order-sign\",\n" +
|
||||||
|
" \"title\" : \"ASLPCrd Workflow\",\n" +
|
||||||
|
" \"description\" : \"An example workflow for the CRD step of DaVinci Burden Reduction.\",\n" +
|
||||||
|
" \"id\" : \"ASLPCrd\",\n" +
|
||||||
|
" \"prefetch\" : {\n" +
|
||||||
|
" \"item1\" : \"Patient?_id=Patient/{{context.patientId}}\",\n" +
|
||||||
|
" \"item2\" : \"ServiceRequest?patient=Patient/{{context.patientId}}\",\n" +
|
||||||
|
" \"item3\" : \"Condition?patient=Patient/{{context.patientId}}&code=http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes|ASLP.A1.DE19\",\n" +
|
||||||
|
" \"item4\" : \"Condition?patient=Patient/{{context.patientId}}&code=http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes|ASLP.A1.DE18\",\n" +
|
||||||
|
" \"item5\" : \"Observation?subject=Patient/{{context.patientId}}&code=http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes|ASLP.A1.DE19\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package ca.uhn.hapi.fhir.cdshooks.svc.cr.resolution;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||||
|
import ca.uhn.fhir.util.ClasspathUtil;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.module.CdsHooksObjectMapperFactory;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.BaseCrTest;
|
||||||
|
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrServiceR4;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
|
import org.hl7.fhir.r4.model.codesystems.ActionType;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.opencds.cqf.fhir.api.Repository;
|
||||||
|
import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class CdsCrServiceR4Test extends BaseCrTest {
|
||||||
|
private ObjectMapper myObjectMapper;@BeforeEach
|
||||||
|
public void loadJson() throws IOException {
|
||||||
|
myObjectMapper = new CdsHooksObjectMapperFactory(myFhirContext).newMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testR4Params() throws IOException {
|
||||||
|
final String rawRequest = ClasspathUtil.loadResource("ASLPCrdServiceRequest.json");
|
||||||
|
final CdsServiceRequestJson cdsServiceRequestJson = myObjectMapper.readValue(rawRequest, CdsServiceRequestJson.class);
|
||||||
|
final Bundle bundle = ClasspathUtil.loadResource(myFhirContext, Bundle.class, "Bundle-ASLPCrd-Content.json");
|
||||||
|
final Repository repository = new InMemoryFhirRepository(myFhirContext, bundle);
|
||||||
|
final RequestDetails requestDetails = new SystemRequestDetails();
|
||||||
|
final IdType planDefinitionId = new IdType(PLAN_DEFINITION_RESOURCE_NAME, "ASLPCrd");
|
||||||
|
requestDetails.setId(planDefinitionId);
|
||||||
|
final Parameters params = new CdsCrServiceR4(requestDetails, repository).encodeParams(cdsServiceRequestJson);
|
||||||
|
|
||||||
|
assertTrue(params.getParameter().size() == 3);
|
||||||
|
assertTrue(params.getParameter("parameters").hasResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testR4Response() {
|
||||||
|
final Bundle bundle = ClasspathUtil.loadResource(myFhirContext, Bundle.class, "Bundle-ASLPCrd-Content.json");
|
||||||
|
final Repository repository = new InMemoryFhirRepository(myFhirContext, bundle);
|
||||||
|
final Bundle responseBundle = ClasspathUtil.loadResource(myFhirContext, Bundle.class, "Bundle-ASLPCrd-Response.json");
|
||||||
|
final RequestDetails requestDetails = new SystemRequestDetails();
|
||||||
|
final IdType planDefinitionId = new IdType(PLAN_DEFINITION_RESOURCE_NAME, "ASLPCrd");
|
||||||
|
requestDetails.setId(planDefinitionId);
|
||||||
|
final CdsServiceResponseJson cdsServiceResponseJson = new CdsCrServiceR4(requestDetails, repository).encodeResponse(responseBundle);
|
||||||
|
|
||||||
|
assertTrue(cdsServiceResponseJson.getCards().size() == 1);
|
||||||
|
assertTrue(!cdsServiceResponseJson.getCards().get(0).getSummary().isEmpty());
|
||||||
|
assertTrue(!cdsServiceResponseJson.getCards().get(0).getDetail().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled // Disabled until the CDS on FHIR specification details how to map system actions.
|
||||||
|
public void testSystemActionResponse() {
|
||||||
|
final Bundle bundle = ClasspathUtil.loadResource(myFhirContext, Bundle.class, "Bundle-DischargeInstructionsPlan-Content.json");
|
||||||
|
final Repository repository = new InMemoryFhirRepository(myFhirContext, bundle);
|
||||||
|
final Bundle responseBundle = ClasspathUtil.loadResource(myFhirContext, Bundle.class, "Bundle-DischargeInstructionsPlan-Response.json");
|
||||||
|
final RequestDetails requestDetails = new SystemRequestDetails();
|
||||||
|
final IdType planDefinitionId = new IdType(PLAN_DEFINITION_RESOURCE_NAME, "DischargeInstructionsPlan");
|
||||||
|
requestDetails.setId(planDefinitionId);
|
||||||
|
final CdsServiceResponseJson cdsServiceResponseJson = new CdsCrServiceR4(requestDetails, repository).encodeResponse(responseBundle);
|
||||||
|
|
||||||
|
assertTrue(cdsServiceResponseJson.getServiceActions().size() == 1);
|
||||||
|
assertTrue(cdsServiceResponseJson.getServiceActions().get(0).getType().equals(ActionType.CREATE.toCode()));
|
||||||
|
assertNotNull(cdsServiceResponseJson.getServiceActions().get(0).getResource());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
{
|
||||||
|
"hook" : "order-sign",
|
||||||
|
"hookInstance": "randomGUIDforthehookevent",
|
||||||
|
"fhirServer" : "https://localhost:8000",
|
||||||
|
"context" : {
|
||||||
|
"patientId" : "Patient/123",
|
||||||
|
"draftOrders" : {
|
||||||
|
"resourceType": "Bundle",
|
||||||
|
"entry": [
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "ServiceRequest",
|
||||||
|
"id": "SleepStudy",
|
||||||
|
"meta": {
|
||||||
|
"profile": [
|
||||||
|
"http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-sleep-study-order",
|
||||||
|
"http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-servicerequest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": "draft",
|
||||||
|
"intent": "order",
|
||||||
|
"code": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE2",
|
||||||
|
"display": "Home sleep apnea testing (HSAT)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"text": "Home sleep apnea testing (HSAT)"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"reference": "Patient/positive"
|
||||||
|
},
|
||||||
|
"authoredOn": "2023-04-06",
|
||||||
|
"reasonReference": [
|
||||||
|
{
|
||||||
|
"reference": "Condition/SleepApnea"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"occurrenceDateTime": "2023-04-10T08:00:00.000Z",
|
||||||
|
"requester": {
|
||||||
|
"reference": "Practitioner/Practitioner-positive"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "ServiceRequest",
|
||||||
|
"id": "SleepStudy2",
|
||||||
|
"meta": {
|
||||||
|
"profile": [
|
||||||
|
"http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-sleep-study-order",
|
||||||
|
"http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-servicerequest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": "draft",
|
||||||
|
"intent": "order",
|
||||||
|
"code": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE14",
|
||||||
|
"display": "Artificial intelligence (AI)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"text": "Artificial intelligence (AI)"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"reference": "Patient/positive"
|
||||||
|
},
|
||||||
|
"authoredOn": "2023-04-06",
|
||||||
|
"reasonReference": [
|
||||||
|
{
|
||||||
|
"reference": "Condition/SleepApnea"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"occurrenceDateTime": "2023-04-15T08:00:00.000Z",
|
||||||
|
"requester": {
|
||||||
|
"reference": "Practitioner/Practitioner-positive"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,388 @@
|
||||||
|
{
|
||||||
|
"resourceType": "Bundle",
|
||||||
|
"id": "ASLPCrd",
|
||||||
|
"type": "collection",
|
||||||
|
"entry": [
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "RequestGroup",
|
||||||
|
"id": "ASLPCrd",
|
||||||
|
"instantiatesCanonical": [
|
||||||
|
"http://example.org/sdh/dtr/aslp/PlanDefinition/ASLPCrd|1.0.0"
|
||||||
|
],
|
||||||
|
"status": "draft",
|
||||||
|
"intent": "proposal",
|
||||||
|
"subject": {
|
||||||
|
"reference": "positive"
|
||||||
|
},
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"extension": [
|
||||||
|
{
|
||||||
|
"url": null,
|
||||||
|
"valueString": "warning"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Patient requires prior authoriztion for a sleep study",
|
||||||
|
"description": "Patient requires prior authorization due to: history of diabetes. Please open your DTR application and complete Questionniare",
|
||||||
|
"resource": {
|
||||||
|
"reference": "Task/ASLPCrd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "Task",
|
||||||
|
"id": "ASLPCrd",
|
||||||
|
"meta": {
|
||||||
|
"versionId": "1"
|
||||||
|
},
|
||||||
|
"extension": [
|
||||||
|
{
|
||||||
|
"url": "http://hl7.org/fhir/aphl/StructureDefinition/condition",
|
||||||
|
"valueExpression": {
|
||||||
|
"language": "text/cql-identifier",
|
||||||
|
"expression": "Is Prior Auth Required"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://hl7.org/fhir/aphl/StructureDefinition/input",
|
||||||
|
"valueDataRequirement": {
|
||||||
|
"type": "ServiceRequest",
|
||||||
|
"profile": [
|
||||||
|
"http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-sleep-study-order"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"basedOn": [
|
||||||
|
{
|
||||||
|
"reference": "RequestGroup/ASLPCrd",
|
||||||
|
"type": "RequestGroup"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "draft",
|
||||||
|
"intent": "proposal",
|
||||||
|
"code": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"system": "http://hl7.org/fhir/uv/cpg/CodeSystem/cpg-activity-type",
|
||||||
|
"code": "collect-information",
|
||||||
|
"display": "Collect Information"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"for": {
|
||||||
|
"reference": "positive"
|
||||||
|
},
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"type": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"code": "collect-information"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"valueCanonical": "http://example.org/sdh/dtr/aslp/Questionnaire/ASLPA1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"code": "collect-information"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"valueCanonical": "http://example.org/sdh/dtr/aslp/Questionnaire/ASLPA2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "Questionnaire",
|
||||||
|
"id": "ASLPCrd",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "1",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-sleep-study-order",
|
||||||
|
"text": "ASLP Sleep Study Order",
|
||||||
|
"type": "group",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "1.1",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-sleep-study-order#ServiceRequest.code",
|
||||||
|
"text": "Sleep Study",
|
||||||
|
"type": "choice",
|
||||||
|
"required": true,
|
||||||
|
"answerOption": [
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE2",
|
||||||
|
"display": "Home sleep apnea testing (HSAT)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE3",
|
||||||
|
"display": "Peripheral artery tonometry (PAT)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE4",
|
||||||
|
"display": "Actigraphy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE5",
|
||||||
|
"display": "Prescreening devices or procedures"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE6",
|
||||||
|
"display": "Acoustic pharyngometry"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE7",
|
||||||
|
"display": "Digital therapeutics"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE8",
|
||||||
|
"display": "Home oximetry monitoring"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE9",
|
||||||
|
"display": "Polysomnogram"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE10",
|
||||||
|
"display": "Facility-based positive airway pressure (PAP) titration study"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE11",
|
||||||
|
"display": "Facility-based, daytime, abbreviated, cardiorespiratory sleep studies (PAP NAP testing)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE12",
|
||||||
|
"display": "Multiple sleep latency test (MSLT)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE13",
|
||||||
|
"display": "Maintenance of wakefulness test (MWT)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE14",
|
||||||
|
"display": "Artificial intelligence (AI)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"initial": [
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE2",
|
||||||
|
"display": "Home sleep apnea testing (HSAT)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE14",
|
||||||
|
"display": "Artificial intelligence (AI)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "1.2",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-sleep-study-order#ServiceRequest.occurrence[x]",
|
||||||
|
"text": "Sleep Study Date",
|
||||||
|
"type": "dateTime",
|
||||||
|
"required": true,
|
||||||
|
"initial": [
|
||||||
|
{
|
||||||
|
"valueDateTime": "2023-04-10T08:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valueDateTime": "2023-04-15T08:00:00.000Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "2",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-diagnosis-of-obstructive-sleep-apnea",
|
||||||
|
"text": "ASLP Diagnosis of Obstructive Sleep Apnea",
|
||||||
|
"type": "group",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "2.1",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-diagnosis-of-obstructive-sleep-apnea#Condition.code",
|
||||||
|
"text": "Diagnosis of Obstructive Sleep Apnea",
|
||||||
|
"type": "choice",
|
||||||
|
"required": true,
|
||||||
|
"answerOption": [
|
||||||
|
{
|
||||||
|
"valueCoding": {
|
||||||
|
"system": "http://example.org/sdh/dtr/aslp/CodeSystem/aslp-codes",
|
||||||
|
"code": "ASLP.A1.DE17",
|
||||||
|
"display": "Obstructive sleep apnea (OSA)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "3",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-history-of-hypertension",
|
||||||
|
"text": "ASLP History of Hypertension",
|
||||||
|
"type": "group",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "3.1",
|
||||||
|
"text": "An error occurred during item creation: null",
|
||||||
|
"type": "display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "3.2",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-history-of-hypertension#Observation.value[x]",
|
||||||
|
"text": "History of Hypertension",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "4",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-history-of-diabetes",
|
||||||
|
"text": "ASLP History of Diabetes",
|
||||||
|
"type": "group",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "4.1",
|
||||||
|
"text": "An error occurred during item creation: null",
|
||||||
|
"type": "display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "4.2",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-history-of-diabetes#Observation.value[x]",
|
||||||
|
"text": "History of Diabetes",
|
||||||
|
"type": "boolean",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "5",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-neck-circumference",
|
||||||
|
"text": "ASLP Neck Circumference",
|
||||||
|
"type": "group",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "5.1",
|
||||||
|
"text": "An error occurred during item creation: null",
|
||||||
|
"type": "display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "5.2",
|
||||||
|
"text": "An error occurred during item creation: Unknown QuestionnaireItemType code 'Quantity'",
|
||||||
|
"type": "display"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "6",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-height",
|
||||||
|
"text": "ASLP Height",
|
||||||
|
"type": "group",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "6.1",
|
||||||
|
"text": "An error occurred during item creation: null",
|
||||||
|
"type": "display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "6.2",
|
||||||
|
"text": "An error occurred during item creation: Unknown QuestionnaireItemType code 'Quantity'",
|
||||||
|
"type": "display"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "7",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-weight",
|
||||||
|
"text": "ASLP Weight",
|
||||||
|
"type": "group",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "7.1",
|
||||||
|
"text": "An error occurred during item creation: null",
|
||||||
|
"type": "display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "7.2",
|
||||||
|
"text": "An error occurred during item creation: Unknown QuestionnaireItemType code 'Quantity'",
|
||||||
|
"type": "display"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "8",
|
||||||
|
"definition": "http://example.org/sdh/dtr/aslp/StructureDefinition/aslp-bmi",
|
||||||
|
"text": "ASLP BMI",
|
||||||
|
"type": "group",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"linkId": "8.1",
|
||||||
|
"text": "An error occurred during item creation: null",
|
||||||
|
"type": "display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"linkId": "8.2",
|
||||||
|
"text": "An error occurred during item creation: Unknown QuestionnaireItemType code 'Quantity'",
|
||||||
|
"type": "display"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
{
|
||||||
|
"resourceType": "Bundle",
|
||||||
|
"type": "transaction",
|
||||||
|
"entry": [
|
||||||
|
{
|
||||||
|
"fullUrl": "ActivityDefinition/SendMessageActivity",
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "ActivityDefinition",
|
||||||
|
"id": "SendMessageActivity",
|
||||||
|
"meta": {
|
||||||
|
"profile": [
|
||||||
|
"http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-communicationactivity"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"kind": "CommunicationRequest",
|
||||||
|
"profile": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-communicationrequest",
|
||||||
|
"intent": "proposal",
|
||||||
|
"extension": [
|
||||||
|
{
|
||||||
|
"url": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-knowledgeCapability",
|
||||||
|
"valueCode": "publishable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-knowledgeRepresentationLevel",
|
||||||
|
"valueCode": "structured"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "http://example.org/ActivityDefinition/SendMessageActivity",
|
||||||
|
"name": "SendMessageActivity",
|
||||||
|
"title": "ActivityDefinition SendMessageActivity",
|
||||||
|
"status": "draft",
|
||||||
|
"experimental": true,
|
||||||
|
"publisher": "Example",
|
||||||
|
"jurisdiction": [
|
||||||
|
{
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"code": "001",
|
||||||
|
"system": "http://unstats.un.org/unsd/methods/m49/m49.htm",
|
||||||
|
"display": "World"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Example Activity Definition for a recommendation to send a message",
|
||||||
|
"code": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"code": "send-message",
|
||||||
|
"system": "http://hl7.org/fhir/uv/cpg/CodeSystem/cpg-activity-type",
|
||||||
|
"display": "Send a message"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"doNotPerform": false,
|
||||||
|
"dynamicValue": [
|
||||||
|
{
|
||||||
|
"path": "payload[0].contentString",
|
||||||
|
"expression": {
|
||||||
|
"language": "text/fhirpath",
|
||||||
|
"expression": "'Greeting: Hello! ' + %subject.name.given.first() + ' Message: Example Activity Definition for a recommendation to send a message Practitioner: ' + %practitioner.name.given.first()"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"url": "ActivityDefinition/SendMessageActivity"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fullUrl": "PlanDefinition/DischargeInstructionsPlan",
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "PlanDefinition",
|
||||||
|
"id": "DischargeInstructionsPlan",
|
||||||
|
"meta": {
|
||||||
|
"profile": [
|
||||||
|
"http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-computableplandefinition"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extension": [
|
||||||
|
{
|
||||||
|
"url": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-knowledgeCapability",
|
||||||
|
"valueCode": "publishable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-knowledgeRepresentationLevel",
|
||||||
|
"valueCode": "structured"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "http://example.org/PlanDefinition/DischargeInstructionsPlan",
|
||||||
|
"name": "DischargeInstructionsPlan",
|
||||||
|
"title": "PlanDefinition DischargeInstructionsPlan",
|
||||||
|
"status": "draft",
|
||||||
|
"experimental": true,
|
||||||
|
"publisher": "Example",
|
||||||
|
"jurisdiction": [
|
||||||
|
{
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"code": "001",
|
||||||
|
"system": "http://unstats.un.org/unsd/methods/m49/m49.htm",
|
||||||
|
"display": "World"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Provide patient discharge instructions",
|
||||||
|
"type": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"code": "clinical-protocol",
|
||||||
|
"system": "http://terminology.hl7.org/CodeSystem/plan-definition-type",
|
||||||
|
"display": "Clinical Protocol"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"title": "Send message with discharge instructions",
|
||||||
|
"code": [
|
||||||
|
{
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"code": "provide-counseling",
|
||||||
|
"system": "http://hl7.org/fhir/uv/cpg/CodeSystem/cpg-common-process",
|
||||||
|
"display": "Provide Counseling"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"code": "create",
|
||||||
|
"system": "http://terminology.hl7.org/CodeSystem/action-type"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dynamicValue": [
|
||||||
|
{
|
||||||
|
"path": "payload[0].contentString",
|
||||||
|
"expression": {
|
||||||
|
"language": "text/fhirpath",
|
||||||
|
"expression": "'Provide patient discharge instructions for ' + %subject.name.given.first()"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"definitionCanonical": "http://example.org/ActivityDefinition/SendMessageActivity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"url": "PlanDefinition/DischargeInstructionsPlan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
{
|
||||||
|
"resourceType": "Bundle",
|
||||||
|
"id": "DischargeInstructionsPlan",
|
||||||
|
"type": "collection",
|
||||||
|
"entry": [
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "RequestGroup",
|
||||||
|
"id": "DischargeInstructionsPlan",
|
||||||
|
"meta": {
|
||||||
|
"profile": [
|
||||||
|
"http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-strategy"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"instantiatesCanonical": [
|
||||||
|
"http://example.org/PlanDefinition/DischargeInstructionsPlan|0.1.0"
|
||||||
|
],
|
||||||
|
"status": "draft",
|
||||||
|
"intent": "proposal",
|
||||||
|
"subject": {
|
||||||
|
"reference": "Patient/Patient1"
|
||||||
|
},
|
||||||
|
"encounter": {
|
||||||
|
"reference": "Encounter/Encounter1"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"reference": "Practitioner/Practitioner1"
|
||||||
|
},
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"title": "Send message with discharge instructions",
|
||||||
|
"code": [
|
||||||
|
{
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"system": "http://hl7.org/fhir/uv/cpg/CodeSystem/cpg-common-process",
|
||||||
|
"code": "provide-counseling",
|
||||||
|
"display": "Provide Counseling"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"system": "http://terminology.hl7.org/CodeSystem/action-type",
|
||||||
|
"code": "create"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"reference": "CommunicationRequest/SendMessageActivity"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"resourceType": "CommunicationRequest",
|
||||||
|
"id": "SendMessageActivity",
|
||||||
|
"meta": {
|
||||||
|
"versionId": "2",
|
||||||
|
"profile": [
|
||||||
|
"http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-communicationrequest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": "draft",
|
||||||
|
"doNotPerform": false,
|
||||||
|
"subject": {
|
||||||
|
"reference": "Patient/Patient1"
|
||||||
|
},
|
||||||
|
"encounter": {
|
||||||
|
"reference": "Encounter/Encounter1"
|
||||||
|
},
|
||||||
|
"payload": [
|
||||||
|
{
|
||||||
|
"contentString": "Provide patient discharge instructions for Alice"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requester": {
|
||||||
|
"reference": "Practitioner/Practitioner1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||||
import ca.uhn.fhir.rest.server.ElementsSupportEnum;
|
import ca.uhn.fhir.rest.server.ElementsSupportEnum;
|
||||||
import ca.uhn.fhir.rest.server.IPagingProvider;
|
import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||||
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.ImmutableListMultimap;
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
@ -72,6 +73,7 @@ public class SystemRequestDetails extends RequestDetails {
|
||||||
super(theDetails);
|
super(theDetails);
|
||||||
if (nonNull(theDetails.getServer())) {
|
if (nonNull(theDetails.getServer())) {
|
||||||
myServer = theDetails.getServer();
|
myServer = theDetails.getServer();
|
||||||
|
myFhirContext = theDetails.getFhirContext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +154,10 @@ public class SystemRequestDetails extends RequestDetails {
|
||||||
return myServer;
|
return myServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setServer(RestfulServer theServer) {
|
||||||
|
this.myServer = theServer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getServerBaseForRequest() {
|
public String getServerBaseForRequest() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Server Framework
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.fhir.rest.api.server;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.server.BaseRestfulResponse;
|
||||||
|
import ca.uhn.fhir.util.IoUtil;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default RestfulResponse that returns the body as an IBaseResource and ignores everything else.
|
||||||
|
*/
|
||||||
|
public class SystemRestfulResponse extends BaseRestfulResponse<SystemRequestDetails> {
|
||||||
|
private Writer myWriter;
|
||||||
|
private ByteArrayOutputStream myOutputStream;
|
||||||
|
|
||||||
|
public SystemRestfulResponse(SystemRequestDetails theSystemRequestDetails) {
|
||||||
|
super(theSystemRequestDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Writer getResponseWriter(int theStatusCode, String theContentType, String theCharset, boolean theRespondGzip)
|
||||||
|
throws IOException {
|
||||||
|
Validate.isTrue(myWriter == null, "getResponseWriter() called multiple times");
|
||||||
|
Validate.isTrue(myOutputStream == null, "getResponseWriter() called after getResponseOutputStream()");
|
||||||
|
|
||||||
|
myWriter = new StringWriter();
|
||||||
|
return myWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public OutputStream getResponseOutputStream(
|
||||||
|
int theStatusCode, String theContentType, @Nullable Integer theContentLength) throws IOException {
|
||||||
|
Validate.isTrue(myWriter == null, "getResponseOutputStream() called multiple times");
|
||||||
|
Validate.isTrue(myOutputStream == null, "getResponseOutputStream() called after getResponseWriter()");
|
||||||
|
|
||||||
|
myOutputStream = new ByteArrayOutputStream();
|
||||||
|
return myOutputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object commitResponse(@Nonnull Closeable theWriterOrOutputStream) throws IOException {
|
||||||
|
IoUtil.closeQuietly(theWriterOrOutputStream);
|
||||||
|
|
||||||
|
return getRequestDetails().getServer().getFhirContext().newJsonParser().parseResource(myWriter.toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -139,6 +139,12 @@
|
||||||
<version>${spring-security-core.version}</version>
|
<version>${spring-security-core.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||||
|
<version>${spring_boot_version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- This is needed for the CqlExceptionHandlingInterceptor -->
|
<!-- This is needed for the CqlExceptionHandlingInterceptor -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
|
|
|
@ -23,10 +23,12 @@ import ca.uhn.fhir.cr.common.IRepositoryFactory;
|
||||||
import ca.uhn.fhir.cr.repo.HapiFhirRepository;
|
import ca.uhn.fhir.cr.repo.HapiFhirRepository;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@ConditionalOnBean(RestfulServer.class)
|
||||||
public class RepositoryConfig {
|
public class RepositoryConfig {
|
||||||
@Bean
|
@Bean
|
||||||
IRepositoryFactory repositoryFactory(DaoRegistry theDaoRegistry, RestfulServer theRestfulServer) {
|
IRepositoryFactory repositoryFactory(DaoRegistry theDaoRegistry, RestfulServer theRestfulServer) {
|
||||||
|
|
|
@ -26,14 +26,17 @@ import ca.uhn.fhir.cr.config.ProviderLoader;
|
||||||
import ca.uhn.fhir.cr.config.ProviderSelector;
|
import ca.uhn.fhir.cr.config.ProviderSelector;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnBean({IRepositoryFactory.class, RestfulServer.class, EvaluationSettings.class})
|
||||||
public class ApplyOperationConfig {
|
public class ApplyOperationConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ca.uhn.fhir.cr.dstu3.IActivityDefinitionProcessorFactory dstu3ActivityDefinitionProcessorFactory(
|
ca.uhn.fhir.cr.dstu3.IActivityDefinitionProcessorFactory dstu3ActivityDefinitionProcessorFactory(
|
||||||
IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
|
IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
|
||||||
|
@ -61,7 +64,6 @@ public class ApplyOperationConfig {
|
||||||
@Bean(name = "applyOperationLoader")
|
@Bean(name = "applyOperationLoader")
|
||||||
public ProviderLoader applyOperationLoader(
|
public ProviderLoader applyOperationLoader(
|
||||||
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
||||||
|
|
||||||
var selector = new ProviderSelector(
|
var selector = new ProviderSelector(
|
||||||
theFhirContext,
|
theFhirContext,
|
||||||
Map.of(
|
Map.of(
|
||||||
|
|
|
@ -26,12 +26,16 @@ import ca.uhn.fhir.cr.config.ProviderLoader;
|
||||||
import ca.uhn.fhir.cr.config.ProviderSelector;
|
import ca.uhn.fhir.cr.config.ProviderSelector;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnBean({IRepositoryFactory.class, RestfulServer.class, EvaluationSettings.class})
|
||||||
public class ExtractOperationConfig {
|
public class ExtractOperationConfig {
|
||||||
@Bean
|
@Bean
|
||||||
ca.uhn.fhir.cr.dstu3.IQuestionnaireResponseProcessorFactory dstu3QuestionnaireResponseProcessorFactory(
|
ca.uhn.fhir.cr.dstu3.IQuestionnaireResponseProcessorFactory dstu3QuestionnaireResponseProcessorFactory(
|
||||||
|
@ -49,7 +53,6 @@ public class ExtractOperationConfig {
|
||||||
@Bean(name = "extractOperationLoader")
|
@Bean(name = "extractOperationLoader")
|
||||||
public ProviderLoader extractOperationLoader(
|
public ProviderLoader extractOperationLoader(
|
||||||
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
||||||
|
|
||||||
var selector = new ProviderSelector(
|
var selector = new ProviderSelector(
|
||||||
theFhirContext,
|
theFhirContext,
|
||||||
Map.of(
|
Map.of(
|
||||||
|
|
|
@ -26,12 +26,16 @@ import ca.uhn.fhir.cr.config.ProviderLoader;
|
||||||
import ca.uhn.fhir.cr.config.ProviderSelector;
|
import ca.uhn.fhir.cr.config.ProviderSelector;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnBean({IRepositoryFactory.class, RestfulServer.class, EvaluationSettings.class})
|
||||||
public class PackageOperationConfig {
|
public class PackageOperationConfig {
|
||||||
@Bean
|
@Bean
|
||||||
ca.uhn.fhir.cr.dstu3.IPlanDefinitionProcessorFactory dstu3PlanDefinitionProcessorFactory(
|
ca.uhn.fhir.cr.dstu3.IPlanDefinitionProcessorFactory dstu3PlanDefinitionProcessorFactory(
|
||||||
|
@ -60,7 +64,6 @@ public class PackageOperationConfig {
|
||||||
@Bean(name = "packageOperationLoader")
|
@Bean(name = "packageOperationLoader")
|
||||||
public ProviderLoader packageOperationLoader(
|
public ProviderLoader packageOperationLoader(
|
||||||
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
||||||
|
|
||||||
var selector = new ProviderSelector(
|
var selector = new ProviderSelector(
|
||||||
theFhirContext,
|
theFhirContext,
|
||||||
Map.of(
|
Map.of(
|
||||||
|
|
|
@ -26,12 +26,16 @@ import ca.uhn.fhir.cr.config.ProviderLoader;
|
||||||
import ca.uhn.fhir.cr.config.ProviderSelector;
|
import ca.uhn.fhir.cr.config.ProviderSelector;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnBean({IRepositoryFactory.class, RestfulServer.class, EvaluationSettings.class})
|
||||||
public class PopulateOperationConfig {
|
public class PopulateOperationConfig {
|
||||||
@Bean
|
@Bean
|
||||||
ca.uhn.fhir.cr.dstu3.IQuestionnaireProcessorFactory dstu3QuestionnaireProcessorFactory(
|
ca.uhn.fhir.cr.dstu3.IQuestionnaireProcessorFactory dstu3QuestionnaireProcessorFactory(
|
||||||
|
@ -48,7 +52,6 @@ public class PopulateOperationConfig {
|
||||||
@Bean(name = "populateOperationLoader")
|
@Bean(name = "populateOperationLoader")
|
||||||
public ProviderLoader populateOperationLoader(
|
public ProviderLoader populateOperationLoader(
|
||||||
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
||||||
|
|
||||||
var selector = new ProviderSelector(
|
var selector = new ProviderSelector(
|
||||||
theFhirContext,
|
theFhirContext,
|
||||||
Map.of(
|
Map.of(
|
||||||
|
|
|
@ -26,14 +26,17 @@ import ca.uhn.fhir.cr.config.ProviderLoader;
|
||||||
import ca.uhn.fhir.cr.config.ProviderSelector;
|
import ca.uhn.fhir.cr.config.ProviderSelector;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnBean({IRepositoryFactory.class, RestfulServer.class, EvaluationSettings.class})
|
||||||
public class ApplyOperationConfig {
|
public class ApplyOperationConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ca.uhn.fhir.cr.r4.IActivityDefinitionProcessorFactory r4ActivityDefinitionProcessorFactory(
|
ca.uhn.fhir.cr.r4.IActivityDefinitionProcessorFactory r4ActivityDefinitionProcessorFactory(
|
||||||
IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
|
IRepositoryFactory theRepositoryFactory, EvaluationSettings theEvaluationSettings) {
|
||||||
|
@ -61,7 +64,6 @@ public class ApplyOperationConfig {
|
||||||
@Bean(name = "applyOperationLoader")
|
@Bean(name = "applyOperationLoader")
|
||||||
public ProviderLoader applyOperationLoader(
|
public ProviderLoader applyOperationLoader(
|
||||||
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
||||||
|
|
||||||
var selector = new ProviderSelector(
|
var selector = new ProviderSelector(
|
||||||
theFhirContext,
|
theFhirContext,
|
||||||
Map.of(
|
Map.of(
|
||||||
|
|
|
@ -26,12 +26,16 @@ import ca.uhn.fhir.cr.config.ProviderLoader;
|
||||||
import ca.uhn.fhir.cr.config.ProviderSelector;
|
import ca.uhn.fhir.cr.config.ProviderSelector;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnBean({IRepositoryFactory.class, RestfulServer.class, EvaluationSettings.class})
|
||||||
public class ExtractOperationConfig {
|
public class ExtractOperationConfig {
|
||||||
@Bean
|
@Bean
|
||||||
ca.uhn.fhir.cr.r4.IQuestionnaireResponseProcessorFactory r4QuestionnaireResponseProcessorFactory(
|
ca.uhn.fhir.cr.r4.IQuestionnaireResponseProcessorFactory r4QuestionnaireResponseProcessorFactory(
|
||||||
|
@ -49,7 +53,6 @@ public class ExtractOperationConfig {
|
||||||
@Bean(name = "extractOperationLoader")
|
@Bean(name = "extractOperationLoader")
|
||||||
public ProviderLoader extractOperationLoader(
|
public ProviderLoader extractOperationLoader(
|
||||||
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
||||||
|
|
||||||
var selector = new ProviderSelector(
|
var selector = new ProviderSelector(
|
||||||
theFhirContext,
|
theFhirContext,
|
||||||
Map.of(
|
Map.of(
|
||||||
|
|
|
@ -26,12 +26,16 @@ import ca.uhn.fhir.cr.config.ProviderLoader;
|
||||||
import ca.uhn.fhir.cr.config.ProviderSelector;
|
import ca.uhn.fhir.cr.config.ProviderSelector;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnBean({IRepositoryFactory.class, RestfulServer.class, EvaluationSettings.class})
|
||||||
public class PackageOperationConfig {
|
public class PackageOperationConfig {
|
||||||
@Bean
|
@Bean
|
||||||
ca.uhn.fhir.cr.r4.IPlanDefinitionProcessorFactory r4PlanDefinitionProcessorFactory(
|
ca.uhn.fhir.cr.r4.IPlanDefinitionProcessorFactory r4PlanDefinitionProcessorFactory(
|
||||||
|
@ -60,7 +64,6 @@ public class PackageOperationConfig {
|
||||||
@Bean(name = "packageOperationLoader")
|
@Bean(name = "packageOperationLoader")
|
||||||
public ProviderLoader packageOperationLoader(
|
public ProviderLoader packageOperationLoader(
|
||||||
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
||||||
|
|
||||||
var selector = new ProviderSelector(
|
var selector = new ProviderSelector(
|
||||||
theFhirContext,
|
theFhirContext,
|
||||||
Map.of(
|
Map.of(
|
||||||
|
|
|
@ -26,12 +26,16 @@ import ca.uhn.fhir.cr.config.ProviderLoader;
|
||||||
import ca.uhn.fhir.cr.config.ProviderSelector;
|
import ca.uhn.fhir.cr.config.ProviderSelector;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
import org.opencds.cqf.fhir.cql.EvaluationSettings;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnBean({IRepositoryFactory.class, RestfulServer.class, EvaluationSettings.class})
|
||||||
public class PopulateOperationConfig {
|
public class PopulateOperationConfig {
|
||||||
@Bean
|
@Bean
|
||||||
ca.uhn.fhir.cr.r4.IQuestionnaireProcessorFactory r4QuestionnaireProcessorFactory(
|
ca.uhn.fhir.cr.r4.IQuestionnaireProcessorFactory r4QuestionnaireProcessorFactory(
|
||||||
|
@ -48,7 +52,6 @@ public class PopulateOperationConfig {
|
||||||
@Bean(name = "populateOperationLoader")
|
@Bean(name = "populateOperationLoader")
|
||||||
public ProviderLoader populateOperationLoader(
|
public ProviderLoader populateOperationLoader(
|
||||||
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
ApplicationContext theApplicationContext, FhirContext theFhirContext, RestfulServer theRestfulServer) {
|
||||||
|
|
||||||
var selector = new ProviderSelector(
|
var selector = new ProviderSelector(
|
||||||
theFhirContext,
|
theFhirContext,
|
||||||
Map.of(
|
Map.of(
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.cr.repo;
|
package ca.uhn.fhir.cr.repo;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||||
|
@ -43,6 +44,7 @@ class RequestDetailsCloner {
|
||||||
newDetails.setParameters(new HashMap<>());
|
newDetails.setParameters(new HashMap<>());
|
||||||
newDetails.setResourceName(null);
|
newDetails.setResourceName(null);
|
||||||
newDetails.setCompartmentName(null);
|
newDetails.setCompartmentName(null);
|
||||||
|
newDetails.setResponse(theDetails.getResponse());
|
||||||
|
|
||||||
return new DetailsBuilder(newDetails);
|
return new DetailsBuilder(newDetails);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +67,9 @@ class RequestDetailsCloner {
|
||||||
}
|
}
|
||||||
|
|
||||||
DetailsBuilder setParameters(IBaseParameters theParameters) {
|
DetailsBuilder setParameters(IBaseParameters theParameters) {
|
||||||
myDetails.setResource(theParameters);
|
IParser parser = myDetails.getServer().getFhirContext().newJsonParser();
|
||||||
|
myDetails.setRequestContents(
|
||||||
|
parser.encodeResourceToString(theParameters).getBytes());
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,10 @@ package ca.uhn.fhir.cr.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.cr.IResourceLoader;
|
import ca.uhn.fhir.cr.IResourceLoader;
|
||||||
|
import ca.uhn.fhir.cr.config.r4.ApplyOperationConfig;
|
||||||
|
import ca.uhn.fhir.cr.config.r4.ExtractOperationConfig;
|
||||||
|
import ca.uhn.fhir.cr.config.r4.PackageOperationConfig;
|
||||||
|
import ca.uhn.fhir.cr.config.r4.PopulateOperationConfig;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
|
@ -35,7 +39,13 @@ import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
||||||
@ContextConfiguration(classes = {TestCrR4Config.class})
|
@ContextConfiguration(classes = {
|
||||||
|
TestCrR4Config.class,
|
||||||
|
ApplyOperationConfig.class,
|
||||||
|
ExtractOperationConfig.class,
|
||||||
|
PackageOperationConfig.class,
|
||||||
|
PopulateOperationConfig.class
|
||||||
|
})
|
||||||
public abstract class BaseCrR4TestServer extends BaseJpaR4Test implements IResourceLoader {
|
public abstract class BaseCrR4TestServer extends BaseJpaR4Test implements IResourceLoader {
|
||||||
|
|
||||||
public static IGenericClient ourClient;
|
public static IGenericClient ourClient;
|
||||||
|
|
|
@ -31,12 +31,7 @@ import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Import({TestCrConfig.class, CrR4Config.class,
|
@Import({TestCrConfig.class, CrR4Config.class})
|
||||||
ApplyOperationConfig.class,
|
|
||||||
ExtractOperationConfig.class,
|
|
||||||
PackageOperationConfig.class,
|
|
||||||
PopulateOperationConfig.class
|
|
||||||
})
|
|
||||||
public class TestCrR4Config {
|
public class TestCrR4Config {
|
||||||
@Primary
|
@Primary
|
||||||
@Bean
|
@Bean
|
||||||
|
|
Loading…
Reference in New Issue