cds hooks allow arbitrary strings for extensions (#6026)
* changes to context booter and basecdsservicejson * cleanup CdsHooksContextBooter * javadocs * change JsonNode to CdsHooksExtension * failing test for service extensions... * fix extension serialization * fix extension serialization * failing test for request extensions not getting parsed... * try adding a custom deserializer for extension * try adding a custom deserializer for extension * try adding a custom deserializer for extension * wire up CdsServiceRegistryImpl * fix wiring object mapper * merge master * get hook in deserializer * create CdsServiceRequestJsonDeserializer.. * spotless * fix for cds service feedback for CdsHooksControllerTest.java * spotless * cleanup... * spotless... * docs * enable tests for CdsHooksContextBooterTest * more cleanup... * refactor CdsServiceRequestJsonDeserializer * apply suggestion * spotless.... * fix checkstyle... * spotless * split custom extension classes into its own package for tests * add changelog * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6023-fix-allowing-arbitrary-json-for-cds-hooks-extensions.yaml Co-authored-by: Ken Stevens <khstevens@gmail.com> * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6023-fix-allowing-arbitrary-json-for-cds-hooks-extensions.yaml Co-authored-by: Ken Stevens <khstevens@gmail.com> * Update hapi-fhir-server-cds-hooks/src/main/java/ca/uhn/hapi/fhir/cdshooks/api/json/CdsHooksExtension.java Co-authored-by: Ken Stevens <khstevens@gmail.com> * fix comments * rename * fix the comment * spotless * version bump to 7.3.9-SNAPSHOT --------- Co-authored-by: Ken Stevens <khstevens@gmail.com>
This commit is contained in:
parent
3ffb695b6b
commit
ce0160e7f9
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-bom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
<name>HAPI FHIR BOM</name>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-cli</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: change
|
||||
issue: 6024
|
||||
title: "Previously, CDS hook extensions needed to be encoded as strings. This has been changed so that extensions are now properly provided as inline JSON."
|
|
@ -11,7 +11,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
package ca.uhn.hapi.fhir.cdshooks.api;
|
||||
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
@ -72,4 +74,6 @@ public @interface CdsService {
|
|||
* An arbitrary string which will be used to store stringify JSON
|
||||
*/
|
||||
String extension() default "";
|
||||
|
||||
Class<? extends CdsHooksExtension> extensionClass() default CdsHooksExtension.class;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ public interface ICdsServiceRegistry {
|
|||
* @param theCdsServiceFeedbackJson the request
|
||||
* @return the response
|
||||
*/
|
||||
String callFeedback(String theServiceId, CdsServiceFeedbackJson theCdsServiceFeedbackJson);
|
||||
CdsServiceFeedbackJson callFeedback(String theServiceId, CdsServiceFeedbackJson theCdsServiceFeedbackJson);
|
||||
|
||||
/**
|
||||
* Register a new CDS Service with the endpoint.
|
||||
|
@ -86,4 +86,12 @@ public interface ICdsServiceRegistry {
|
|||
* @param theServiceId the id of the service to be removed
|
||||
*/
|
||||
void unregisterService(String theServiceId, String theModuleId);
|
||||
|
||||
/**
|
||||
* Get registered CDS service with service ID
|
||||
* @param theServiceId the id of the service to be retrieved
|
||||
* @return CdsServiceJson
|
||||
* @throws IllegalArgumentException if a CDS service with provided serviceId is not found
|
||||
*/
|
||||
CdsServiceJson getCdsServiceJson(String theServiceId);
|
||||
}
|
||||
|
|
|
@ -24,18 +24,29 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
|
||||
/**
|
||||
* @see <a href=" https://cds-hooks.hl7.org/1.0/#extensions">For reading more about Extension support in CDS hooks</a>
|
||||
* Example can be found <a href="https://build.fhir.org/ig/HL7/davinci-crd/deviations.html#configuration-options-extension">here</a>
|
||||
*/
|
||||
public abstract class BaseCdsServiceJson implements IModelJson {
|
||||
|
||||
@JsonProperty(value = "extension", required = true)
|
||||
String myExtension;
|
||||
@JsonProperty(value = "extension")
|
||||
CdsHooksExtension myExtension;
|
||||
|
||||
public String getExtension() {
|
||||
private Class<? extends CdsHooksExtension> myExtensionClass;
|
||||
|
||||
public CdsHooksExtension getExtension() {
|
||||
return myExtension;
|
||||
}
|
||||
|
||||
public BaseCdsServiceJson setExtension(String theExtension) {
|
||||
public BaseCdsServiceJson setExtension(CdsHooksExtension theExtension) {
|
||||
this.myExtension = theExtension;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setExtensionClass(Class<? extends CdsHooksExtension> theClass) {
|
||||
this.myExtensionClass = theClass;
|
||||
}
|
||||
|
||||
public Class<? extends CdsHooksExtension> getExtensionClass() {
|
||||
return myExtensionClass;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.api.json;
|
||||
|
||||
import ca.uhn.fhir.model.api.IModelJson;
|
||||
|
||||
/**
|
||||
* Users can define CDS Hooks extensions by extending this class.
|
||||
* Implementors can extend this class for defining their custom extensions.
|
||||
*/
|
||||
public class CdsHooksExtension implements IModelJson {}
|
|
@ -98,13 +98,15 @@ public class CdsHooksConfig {
|
|||
CdsPrefetchSvc theCdsPrefetchSvc,
|
||||
@Qualifier(CDS_HOOKS_OBJECT_MAPPER_FACTORY) ObjectMapper theObjectMapper,
|
||||
ICdsCrServiceFactory theCdsCrServiceFactory,
|
||||
ICrDiscoveryServiceFactory theCrDiscoveryServiceFactory) {
|
||||
ICrDiscoveryServiceFactory theCrDiscoveryServiceFactory,
|
||||
FhirContext theFhirContext) {
|
||||
return new CdsServiceRegistryImpl(
|
||||
theCdsHooksContextBooter,
|
||||
theCdsPrefetchSvc,
|
||||
theObjectMapper,
|
||||
theCdsCrServiceFactory,
|
||||
theCrDiscoveryServiceFactory);
|
||||
theCrDiscoveryServiceFactory,
|
||||
theFhirContext);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
|
@ -94,13 +94,12 @@ public class CdsHooksController {
|
|||
path = "{cds_hook}/feedback",
|
||||
method = {RequestMethod.POST},
|
||||
consumes = {MediaType.APPLICATION_JSON_VALUE})
|
||||
public ResponseEntity<String> cdsServiceFeedback(
|
||||
public ResponseEntity<CdsServiceFeedbackJson> cdsServiceFeedback(
|
||||
@PathVariable("cds_hook") String theCdsHook,
|
||||
@RequestBody CdsServiceFeedbackJson theCdsServiceFeedbackJson) {
|
||||
String json = myCdsServiceRegistry.callFeedback(theCdsHook, theCdsServiceFeedbackJson);
|
||||
|
||||
CdsServiceFeedbackJson response = myCdsServiceRegistry.callFeedback(theCdsHook, theCdsServiceFeedbackJson);
|
||||
return ResponseEntity.status(200)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(json);
|
||||
.body(response);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.serializer;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.serializer.FhirResourceDeserializer;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestContextJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.svc.CdsServiceRegistryImpl;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CdsServiceRequestJsonDeserializer extends StdDeserializer<CdsServiceRequestJson> {
|
||||
|
||||
private final CdsServiceRegistryImpl myCdsServiceRegistry;
|
||||
private final ObjectMapper myObjectMapper;
|
||||
private final FhirContext myFhirContext;
|
||||
private final IParser myParser;
|
||||
|
||||
public CdsServiceRequestJsonDeserializer(CdsServiceRegistryImpl theCdsServiceRegistry, FhirContext theFhirContext) {
|
||||
super(CdsServiceRequestJson.class);
|
||||
myCdsServiceRegistry = theCdsServiceRegistry;
|
||||
myFhirContext = theFhirContext;
|
||||
myParser = myFhirContext.newJsonParser().setPrettyPrint(true);
|
||||
// We create a new ObjectMapper instead of using the one from the ApplicationContext to avoid an infinite loop
|
||||
// during deserialization.
|
||||
myObjectMapper = new ObjectMapper();
|
||||
configureObjectMapper(myObjectMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CdsServiceRequestJson deserialize(JsonParser theJsonParser, DeserializationContext theDeserializationContext)
|
||||
throws IOException {
|
||||
final JsonNode cdsServiceRequestJsonNode = theJsonParser.getCodec().readTree(theJsonParser);
|
||||
final JsonNode hookNode = cdsServiceRequestJsonNode.get("hook");
|
||||
final JsonNode extensionNode = cdsServiceRequestJsonNode.get("extension");
|
||||
final JsonNode requestContext = cdsServiceRequestJsonNode.get("context");
|
||||
final CdsServiceRequestJson cdsServiceRequestJson =
|
||||
myObjectMapper.treeToValue(cdsServiceRequestJsonNode, CdsServiceRequestJson.class);
|
||||
if (extensionNode != null) {
|
||||
CdsHooksExtension myRequestExtension = deserializeExtension(hookNode.textValue(), extensionNode.toString());
|
||||
cdsServiceRequestJson.setExtension(myRequestExtension);
|
||||
}
|
||||
if (requestContext != null) {
|
||||
LinkedHashMap<String, Object> map =
|
||||
myObjectMapper.readValue(requestContext.toString(), LinkedHashMap.class);
|
||||
cdsServiceRequestJson.setContext(deserializeRequestContext(map));
|
||||
}
|
||||
return cdsServiceRequestJson;
|
||||
}
|
||||
|
||||
void configureObjectMapper(ObjectMapper theObjectMapper) {
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addDeserializer(IBaseResource.class, new FhirResourceDeserializer(myFhirContext));
|
||||
theObjectMapper.registerModule(module);
|
||||
// set this as we will need to ignore properties which are not defined by specific implementation.
|
||||
theObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
CdsHooksExtension deserializeExtension(String theServiceId, String theExtension) throws JsonProcessingException {
|
||||
final CdsServiceJson cdsServicesJson = myCdsServiceRegistry.getCdsServiceJson(theServiceId);
|
||||
Class<? extends CdsHooksExtension> extensionClass = cdsServicesJson.getExtensionClass();
|
||||
if (extensionClass == null) {
|
||||
return null;
|
||||
}
|
||||
return myObjectMapper.readValue(theExtension, extensionClass);
|
||||
}
|
||||
|
||||
CdsServiceRequestContextJson deserializeRequestContext(LinkedHashMap<String, Object> theMap)
|
||||
throws JsonProcessingException {
|
||||
final CdsServiceRequestContextJson cdsServiceRequestContextJson = new CdsServiceRequestContextJson();
|
||||
for (Map.Entry<String, Object> entry : theMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
// Convert LinkedHashMap entries to Resources
|
||||
if (value instanceof LinkedHashMap) {
|
||||
String json = myObjectMapper.writeValueAsString(value);
|
||||
IBaseResource resource = myParser.parseResource(json);
|
||||
cdsServiceRequestContextJson.put(key, resource);
|
||||
} else {
|
||||
cdsServiceRequestContextJson.put(key, value);
|
||||
}
|
||||
}
|
||||
return cdsServiceRequestContextJson;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
|||
import ca.uhn.hapi.fhir.cdshooks.api.CdsService;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.CdsServiceFeedback;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.CdsServicePrefetch;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
@ -85,7 +86,8 @@ public class CdsHooksContextBooter {
|
|||
cdsServiceJson.setHook(annotation.hook());
|
||||
cdsServiceJson.setDescription(annotation.description());
|
||||
cdsServiceJson.setTitle(annotation.title());
|
||||
cdsServiceJson.setExtension(validateJson(annotation.extension()));
|
||||
cdsServiceJson.setExtension(serializeExtensions(annotation.extension(), annotation.extensionClass()));
|
||||
cdsServiceJson.setExtensionClass(annotation.extensionClass());
|
||||
for (CdsServicePrefetch prefetch : annotation.prefetch()) {
|
||||
cdsServiceJson.addPrefetch(prefetch.value(), prefetch.query());
|
||||
cdsServiceJson.addSource(prefetch.value(), prefetch.source());
|
||||
|
@ -104,14 +106,13 @@ public class CdsHooksContextBooter {
|
|||
}
|
||||
}
|
||||
|
||||
protected String validateJson(String theExtension) {
|
||||
CdsHooksExtension serializeExtensions(String theExtension, Class<? extends CdsHooksExtension> theClass) {
|
||||
if (StringUtils.isEmpty(theExtension)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.readTree(theExtension);
|
||||
return theExtension;
|
||||
return mapper.readValue(theExtension, theClass);
|
||||
} catch (JsonProcessingException e) {
|
||||
final String message = String.format("Invalid JSON: %s", e.getMessage());
|
||||
ourLog.debug(message);
|
||||
|
|
|
@ -131,4 +131,11 @@ public class CdsServiceCache {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CdsServiceJson getCdsServiceJson(String theString) {
|
||||
return myCdsServiceJson.getServices().stream()
|
||||
.filter(x -> x.getId().equals(theString))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,21 +20,26 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.svc;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsMethod;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceMethod;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceRegistry;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceFeedbackJson;
|
||||
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.CdsServiceResponseJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServicesJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.serializer.CdsServiceRequestJsonDeserializer;
|
||||
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 com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
@ -59,10 +64,18 @@ public class CdsServiceRegistryImpl implements ICdsServiceRegistry {
|
|||
CdsPrefetchSvc theCdsPrefetchSvc,
|
||||
ObjectMapper theObjectMapper,
|
||||
ICdsCrServiceFactory theCdsCrServiceFactory,
|
||||
ICrDiscoveryServiceFactory theCrDiscoveryServiceFactory) {
|
||||
ICrDiscoveryServiceFactory theCrDiscoveryServiceFactory,
|
||||
FhirContext theFhirContext) {
|
||||
myCdsHooksContextBooter = theCdsHooksContextBooter;
|
||||
myCdsPrefetchSvc = theCdsPrefetchSvc;
|
||||
myObjectMapper = theObjectMapper;
|
||||
// registering this deserializer here instead of
|
||||
// CdsHooksObjectMapperFactory to avoid circular
|
||||
// dependency
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addDeserializer(
|
||||
CdsServiceRequestJson.class, new CdsServiceRequestJsonDeserializer(this, theFhirContext));
|
||||
myObjectMapper.registerModule(module);
|
||||
myCdsCrServiceFactory = theCdsCrServiceFactory;
|
||||
myCrDiscoveryServiceFactory = theCrDiscoveryServiceFactory;
|
||||
}
|
||||
|
@ -82,61 +95,14 @@ public class CdsServiceRegistryImpl implements ICdsServiceRegistry {
|
|||
ICdsServiceMethod serviceMethod = (ICdsServiceMethod) getCdsServiceMethodOrThrowException(theServiceId);
|
||||
myCdsPrefetchSvc.augmentRequest(theCdsServiceRequestJson, serviceMethod);
|
||||
Object response = serviceMethod.invoke(myObjectMapper, theCdsServiceRequestJson, theServiceId);
|
||||
|
||||
return encodeServiceResponse(theServiceId, response);
|
||||
}
|
||||
|
||||
private CdsServiceResponseJson encodeServiceResponse(String theServiceId, Object result) {
|
||||
String json;
|
||||
if (result instanceof String) {
|
||||
json = (String) result;
|
||||
} else {
|
||||
try {
|
||||
json = myObjectMapper.writeValueAsString(result);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ConfigurationException(
|
||||
Msg.code(2389) + "Failed to json serialize Cds service response of type "
|
||||
+ result.getClass().getName() + " when calling CDS Hook Service " + theServiceId,
|
||||
e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return myObjectMapper.readValue(json, CdsServiceResponseJson.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ConfigurationException(
|
||||
Msg.code(2390) + "Failed to json deserialize Cds service response of type "
|
||||
+ result.getClass().getName() + " when calling CDS Hook Service " + theServiceId
|
||||
+ ". Json: " + json,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ICdsMethod getCdsServiceMethodOrThrowException(String theId) {
|
||||
ICdsMethod retval = myServiceCache.getServiceMethod(theId);
|
||||
if (retval == null) {
|
||||
throw new ResourceNotFoundException(
|
||||
Msg.code(2391) + "No service with id " + theId + " is registered on this server");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ICdsMethod getCdsFeedbackMethodOrThrowException(String theId) {
|
||||
ICdsMethod retval = myServiceCache.getFeedbackMethod(theId);
|
||||
if (retval == null) {
|
||||
throw new ResourceNotFoundException(
|
||||
Msg.code(2392) + "No feedback service with id " + theId + " is registered on this server");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String callFeedback(String theServiceId, CdsServiceFeedbackJson theCdsServiceFeedbackJson) {
|
||||
public CdsServiceFeedbackJson callFeedback(String theServiceId, CdsServiceFeedbackJson theCdsServiceFeedbackJson) {
|
||||
ICdsMethod feedbackMethod = getCdsFeedbackMethodOrThrowException(theServiceId);
|
||||
Object response = feedbackMethod.invoke(myObjectMapper, theCdsServiceFeedbackJson, theServiceId);
|
||||
|
||||
return encodeFeedbackResponse(theServiceId, theCdsServiceFeedbackJson, response);
|
||||
return encodeFeedbackResponse(theServiceId, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -146,6 +112,9 @@ public class CdsServiceRegistryImpl implements ICdsServiceRegistry {
|
|||
CdsServiceJson theCdsServiceJson,
|
||||
boolean theAllowAutoFhirClientPrefetch,
|
||||
String theModuleId) {
|
||||
if (theCdsServiceJson.getExtensionClass() == null) {
|
||||
theCdsServiceJson.setExtensionClass(CdsHooksExtension.class);
|
||||
}
|
||||
myServiceCache.registerDynamicService(
|
||||
theServiceId, theServiceFunction, theCdsServiceJson, theAllowAutoFhirClientPrefetch, theModuleId);
|
||||
}
|
||||
|
@ -171,25 +140,100 @@ public class CdsServiceRegistryImpl implements ICdsServiceRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
private String encodeFeedbackResponse(
|
||||
String theServiceId, CdsServiceFeedbackJson theCdsServiceFeedbackJson, Object response) {
|
||||
if (response instanceof String) {
|
||||
return (String) response;
|
||||
@Override
|
||||
public CdsServiceJson getCdsServiceJson(String theServiceId) {
|
||||
CdsServiceJson cdsServiceJson = myServiceCache.getCdsServiceJson(theServiceId);
|
||||
if (cdsServiceJson == null) {
|
||||
throw new IllegalArgumentException(Msg.code(2536) + "No service with " + theServiceId + " is registered.");
|
||||
}
|
||||
return cdsServiceJson;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ICdsMethod getCdsServiceMethodOrThrowException(String theId) {
|
||||
ICdsMethod retval = myServiceCache.getServiceMethod(theId);
|
||||
if (retval == null) {
|
||||
throw new ResourceNotFoundException(
|
||||
Msg.code(2391) + "No service with id " + theId + " is registered on this server");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
CdsServiceResponseJson encodeServiceResponse(String theServiceId, Object result) {
|
||||
if (result instanceof String) {
|
||||
return buildResponseFromString(theServiceId, result, (String) result);
|
||||
} else {
|
||||
try {
|
||||
// Try to json encode the response
|
||||
return myObjectMapper.writeValueAsString(response);
|
||||
} catch (JsonProcessingException e) {
|
||||
try {
|
||||
ourLog.warn("Failed to deserialize response from {} feedback method", theServiceId, e);
|
||||
// Just send back what we received
|
||||
return myObjectMapper.writeValueAsString(theCdsServiceFeedbackJson);
|
||||
} catch (JsonProcessingException f) {
|
||||
ourLog.error("Failed to deserialize request parameter to {} feedback method", theServiceId, e);
|
||||
// Okay then...
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
return buildResponseFromImplementation(theServiceId, result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ICdsMethod getCdsFeedbackMethodOrThrowException(String theId) {
|
||||
ICdsMethod retval = myServiceCache.getFeedbackMethod(theId);
|
||||
if (retval == null) {
|
||||
throw new ResourceNotFoundException(
|
||||
Msg.code(2392) + "No feedback service with id " + theId + " is registered on this server");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
CdsServiceFeedbackJson encodeFeedbackResponse(String theServiceId, Object theResponse) {
|
||||
if (theResponse instanceof String) {
|
||||
return buildFeedbackFromString(theServiceId, (String) theResponse);
|
||||
} else {
|
||||
return buildFeedbackFromImplementation(theServiceId, theResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private CdsServiceResponseJson buildResponseFromImplementation(String theServiceId, Object theResult) {
|
||||
try {
|
||||
return (CdsServiceResponseJson) theResult;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ConfigurationException(
|
||||
Msg.code(2389)
|
||||
+ "Failed to cast Cds service response to CdsServiceResponseJson when calling CDS Hook Service "
|
||||
+ theServiceId + ". The type "
|
||||
+ theResult.getClass().getName()
|
||||
+ " cannot be casted to CdsServiceResponseJson",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private CdsServiceResponseJson buildResponseFromString(String theServiceId, Object theResult, String theJson) {
|
||||
try {
|
||||
return myObjectMapper.readValue(theJson, CdsServiceResponseJson.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ConfigurationException(
|
||||
Msg.code(2390) + "Failed to json deserialize Cds service response of type "
|
||||
+ theResult.getClass().getName() + " when calling CDS Hook Service " + theServiceId
|
||||
+ ". Json: " + theJson,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private CdsServiceFeedbackJson buildFeedbackFromImplementation(String theServiceId, Object theResponse) {
|
||||
try {
|
||||
return (CdsServiceFeedbackJson) theResponse;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(
|
||||
Msg.code(2537) + "Failed to cast feedback response CdsServiceFeedbackJson for service "
|
||||
+ theServiceId + ". " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private CdsServiceFeedbackJson buildFeedbackFromString(String theServiceId, String theResponse) {
|
||||
try {
|
||||
return myObjectMapper.readValue(theResponse, CdsServiceFeedbackJson.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(Msg.code(2538) + "Failed to serialize json Cds Feedback response for service "
|
||||
+ theServiceId + ". " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setServiceCache(CdsServiceCache theCdsServiceCache) {
|
||||
myServiceCache = theCdsServiceCache;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseCardJson;
|
|||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.config.CdsHooksConfig;
|
||||
import ca.uhn.hapi.fhir.cdshooks.config.TestCdsHooksConfig;
|
||||
import ca.uhn.hapi.fhir.cdshooks.custom.extensions.model.RequestExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchFhirClientSvc;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
@ -21,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
@ -30,6 +32,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
|
@ -67,7 +70,9 @@ public class CdsHooksControllerTest {
|
|||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
myMockMvc = MockMvcBuilders.standaloneSetup(new CdsHooksController(myCdsHooksRegistry)).build();
|
||||
myMockMvc = MockMvcBuilders.standaloneSetup(new CdsHooksController(myCdsHooksRegistry))
|
||||
.setMessageConverters(new MappingJackson2HttpMessageConverter(myObjectMapper))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -82,25 +87,29 @@ public class CdsHooksControllerTest {
|
|||
.andExpect(jsonPath("services[2].title").value(GreeterCdsService.TEST_HOOK_TITLE))
|
||||
.andExpect(jsonPath("services[2].id").value(GreeterCdsService.TEST_HOOK_STRING_ID))
|
||||
.andExpect(jsonPath("services[2].prefetch." + GreeterCdsService.TEST_HOOK_PREFETCH_USER_KEY).value(GreeterCdsService.TEST_HOOK_PREFETCH_USER_QUERY))
|
||||
.andExpect(jsonPath("services[2].prefetch." + GreeterCdsService.TEST_HOOK_PREFETCH_PATIENT_KEY).value(GreeterCdsService.TEST_HOOK_PREFETCH_PATIENT_QUERY));
|
||||
.andExpect(jsonPath("services[2].prefetch." + GreeterCdsService.TEST_HOOK_PREFETCH_PATIENT_KEY).value(GreeterCdsService.TEST_HOOK_PREFETCH_PATIENT_QUERY))
|
||||
.andExpect(jsonPath("services[5].extension.example-client-conformance").value("http://hooks.example.org/fhir/102/Conformance/patientview"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExampleFeedback() throws Exception {
|
||||
CdsServiceFeedbackJson request = new CdsServiceFeedbackJson();
|
||||
// setup
|
||||
final CdsServiceFeedbackJson request = new CdsServiceFeedbackJson();
|
||||
request.setCard(TEST_HOOK_INSTANCE);
|
||||
request.setOutcome(CdsServiceFeebackOutcomeEnum.accepted);
|
||||
request.setOutcomeTimestamp(OUTCOME_TIMESTAMP);
|
||||
|
||||
String requestBody = myObjectMapper.writeValueAsString(request);
|
||||
|
||||
myMockMvc
|
||||
final String requestBody = myObjectMapper.writeValueAsString(request);
|
||||
// execute
|
||||
final MvcResult actual = myMockMvc
|
||||
.perform(post(CdsHooksController.BASE + "/example-service/feedback").contentType(MediaType.APPLICATION_JSON).content(requestBody))
|
||||
.andDo(print())
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("message").value("Thank you for your feedback dated " + OUTCOME_TIMESTAMP + "!"))
|
||||
;
|
||||
.andReturn();
|
||||
// validate
|
||||
final CdsServiceFeedbackJson cdsServiceFeedbackJson = myObjectMapper.readValue(actual.getResponse().getContentAsString(), CdsServiceFeedbackJson.class);
|
||||
assertThat(cdsServiceFeedbackJson.getAcceptedSuggestions()).hasSize(1);
|
||||
assertThat(cdsServiceFeedbackJson).usingRecursiveComparison().ignoringFields("myAcceptedSuggestions").isEqualTo(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -127,8 +136,14 @@ public class CdsHooksControllerTest {
|
|||
|
||||
@Test
|
||||
void testCallHelloUniverse() throws Exception {
|
||||
RequestExtension requestExtension = new RequestExtension();
|
||||
requestExtension.setConfigItem("request-config-item");
|
||||
|
||||
CdsServiceRequestJson request = new CdsServiceRequestJson();
|
||||
request.setExtension(requestExtension);
|
||||
request.setFhirServer(TEST_FHIR_SERVER);
|
||||
request.setHook(HelloWorldService.TEST_HOOK_UNIVERSE_ID);
|
||||
|
||||
|
||||
String requestBody = myObjectMapper.writeValueAsString(request);
|
||||
|
||||
|
@ -139,7 +154,7 @@ public class CdsHooksControllerTest {
|
|||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("cards[0].summary").value("Hello Universe!"))
|
||||
.andExpect(jsonPath("cards[0].indicator").value("critical"))
|
||||
;
|
||||
.andExpect(jsonPath("cards[0].extension.example-config-item").value("request-config-item"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -163,21 +178,24 @@ public class CdsHooksControllerTest {
|
|||
|
||||
@Test
|
||||
void testHelloWorldFeedback() throws Exception {
|
||||
CdsServiceFeedbackJson request = new CdsServiceFeedbackJson();
|
||||
// setup
|
||||
final CdsServiceFeedbackJson request = new CdsServiceFeedbackJson();
|
||||
request.setCard(TEST_HOOK_INSTANCE);
|
||||
request.setOutcome(CdsServiceFeebackOutcomeEnum.accepted);
|
||||
request.setOutcomeTimestamp(OUTCOME_TIMESTAMP);
|
||||
|
||||
String requestBody = myObjectMapper.writeValueAsString(request);
|
||||
|
||||
TestServerAppCtx.ourHelloWorldService.setExpectedCount(1);
|
||||
myMockMvc
|
||||
// execute
|
||||
MvcResult actual = myMockMvc
|
||||
.perform(post(CdsHooksController.BASE + "/" + HelloWorldService.TEST_HOOK_WORLD_ID + "/feedback").contentType(MediaType.APPLICATION_JSON).content(requestBody))
|
||||
.andDo(print())
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("message").value("Thank you for your feedback dated " + OUTCOME_TIMESTAMP + "!"))
|
||||
;
|
||||
.andReturn();
|
||||
// validate
|
||||
final CdsServiceFeedbackJson cdsServiceFeedbackJson = myObjectMapper.readValue(actual.getResponse().getContentAsString(), CdsServiceFeedbackJson.class);
|
||||
assertThat(cdsServiceFeedbackJson.getAcceptedSuggestions()).hasSize(1);
|
||||
assertThat(cdsServiceFeedbackJson).usingRecursiveComparison().ignoringFields("myAcceptedSuggestions").isEqualTo(request);
|
||||
TestServerAppCtx.ourHelloWorldService.awaitExpected();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.hapi.fhir.cdshooks.controller;
|
|||
import ca.uhn.hapi.fhir.cdshooks.api.CdsService;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.CdsServiceFeedback;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.CdsServicePrefetch;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceAcceptedSuggestionJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceFeedbackJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceIndicatorEnum;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
||||
|
@ -11,6 +12,9 @@ import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseCardSourceJson;
|
|||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ExampleCdsService {
|
||||
@CdsService(value = "example-service",
|
||||
hook = "patient-view",
|
||||
|
@ -33,7 +37,8 @@ public class ExampleCdsService {
|
|||
}
|
||||
|
||||
@CdsServiceFeedback("example-service")
|
||||
public String exampleServiceFeedback(CdsServiceFeedbackJson theFeedback) {
|
||||
return "{\"message\": \"Thank you for your feedback dated " + theFeedback.getOutcomeTimestamp() + "!\"}";
|
||||
public CdsServiceFeedbackJson feedback(CdsServiceFeedbackJson theFeedback) {
|
||||
theFeedback.setAcceptedSuggestions(List.of(new CdsServiceAcceptedSuggestionJson().setId(UUID.randomUUID().toString())));
|
||||
return theFeedback;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,22 @@ import ca.uhn.fhir.interceptor.api.HookParams;
|
|||
import ca.uhn.hapi.fhir.cdshooks.api.CdsService;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.CdsServiceFeedback;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.CdsServicePrefetch;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceAcceptedSuggestionJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceFeedbackJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceIndicatorEnum;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseCardJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseCardSourceJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.custom.extensions.model.ExampleConfigExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.custom.extensions.model.RequestExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.custom.extensions.model.ResponseExtension;
|
||||
import ca.uhn.test.concurrency.IPointcutLatch;
|
||||
import ca.uhn.test.concurrency.PointcutLatch;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class HelloWorldService implements IPointcutLatch {
|
||||
public static final String TEST_HOOK = "hello-world";
|
||||
|
@ -20,6 +30,9 @@ public class HelloWorldService implements IPointcutLatch {
|
|||
public static final String TEST_HOOK_PLAYBACK_ID = "hwid3";
|
||||
public static final String TEST_HOOK_PREFETCH_PATIENT_KEY = "patient";
|
||||
public static final String TEST_HOOK_PREFETCH_MEDS_KEY = "medications";
|
||||
public static final String CDS_HOOKS_EXTENSION_PROPERTY_PRACTITIONER_SPECIALITY = "myextension-practitionerspecialty";
|
||||
public static final String CDS_HOOKS_EXTENSION_PROPERTY_TIMESTAMP = "timestamp";
|
||||
public static final String CDS_HOOKS_EXTENSION_VALUE_PRACTITIONER_SPECIALITY = "some-speciality";
|
||||
|
||||
private final PointcutLatch myPointcutLatch = new PointcutLatch("Hello World CDS-Hook");
|
||||
|
||||
|
@ -31,23 +44,26 @@ public class HelloWorldService implements IPointcutLatch {
|
|||
@CdsServicePrefetch(value = TEST_HOOK_PREFETCH_PATIENT_KEY, query = "Patient/{{context.patientId}}"),
|
||||
@CdsServicePrefetch(value = TEST_HOOK_PREFETCH_MEDS_KEY, query = "MedicationRequest?patient={{context.patientId}}")
|
||||
})
|
||||
public String helloWorld(String theJson) {
|
||||
return "{\n" +
|
||||
" \"cards\" : [ {\n" +
|
||||
" \"summary\" : \"Hello World!\",\n" +
|
||||
" \"indicator\" : \"warning\",\n" +
|
||||
" \"source\" : {\n" +
|
||||
" \"label\" : \"World Greeter\"\n" +
|
||||
" },\n" +
|
||||
" \"detail\" : \"This is a test. Do not be alarmed.\"\n" +
|
||||
" } ]\n" +
|
||||
"}";
|
||||
public CdsServiceResponseJson helloWorld(String theJson) {
|
||||
final CdsServiceResponseJson cdsServiceResponseJson = new CdsServiceResponseJson();
|
||||
final CdsServiceResponseCardJson cdsServiceResponseCardJson = new CdsServiceResponseCardJson();
|
||||
cdsServiceResponseCardJson.setSummary("Hello World!");
|
||||
cdsServiceResponseCardJson.setIndicator(CdsServiceIndicatorEnum.WARNING);
|
||||
cdsServiceResponseCardJson.setDetail("This is a test. Do not be alarmed.");
|
||||
cdsServiceResponseCardJson.setSource(new CdsServiceResponseCardSourceJson().setLabel("World Greeter"));
|
||||
final ResponseExtension extension = new ResponseExtension();
|
||||
extension.setTimestamp(new Date());
|
||||
extension.setPractitionerSpecialty(CDS_HOOKS_EXTENSION_VALUE_PRACTITIONER_SPECIALITY);
|
||||
cdsServiceResponseCardJson.setExtension(extension);
|
||||
cdsServiceResponseJson.addCard(cdsServiceResponseCardJson);
|
||||
return cdsServiceResponseJson;
|
||||
}
|
||||
|
||||
@CdsServiceFeedback(TEST_HOOK_WORLD_ID)
|
||||
public String feedback(CdsServiceFeedbackJson theFeedback) {
|
||||
public CdsServiceFeedbackJson feedback(CdsServiceFeedbackJson theFeedback) {
|
||||
myPointcutLatch.call(theFeedback);
|
||||
return "{\"message\": \"Thank you for your feedback dated " + theFeedback.getOutcomeTimestamp() + "!\"}";
|
||||
theFeedback.setAcceptedSuggestions(List.of(new CdsServiceAcceptedSuggestionJson().setId(UUID.randomUUID().toString())));
|
||||
return theFeedback;
|
||||
}
|
||||
|
||||
@CdsService(value = TEST_HOOK_UNIVERSE_ID,
|
||||
|
@ -57,18 +73,23 @@ public class HelloWorldService implements IPointcutLatch {
|
|||
prefetch = {
|
||||
@CdsServicePrefetch(value = TEST_HOOK_PREFETCH_PATIENT_KEY, query = "Patient/{{context.patientId}}"),
|
||||
@CdsServicePrefetch(value = TEST_HOOK_PREFETCH_MEDS_KEY, query = "MedicationRequest?patient={{context.patientId}}")
|
||||
})
|
||||
public String helloUniverse(String theJson) {
|
||||
return "{\n" +
|
||||
" \"cards\" : [ {\n" +
|
||||
" \"summary\" : \"Hello Universe!\",\n" +
|
||||
" \"indicator\" : \"critical\",\n" +
|
||||
" \"source\" : {\n" +
|
||||
" \"label\" : \"World Greeter\"\n" +
|
||||
" },\n" +
|
||||
" \"detail\" : \"This is a test. Do not be alarmed.\"\n" +
|
||||
" } ]\n" +
|
||||
"}";
|
||||
},
|
||||
extension = """
|
||||
{
|
||||
"example-config-item": "example-value"
|
||||
}
|
||||
""",
|
||||
extensionClass = RequestExtension.class)
|
||||
public CdsServiceResponseJson helloUniverse(CdsServiceRequestJson theCdsServiceRequestJson) {
|
||||
final CdsServiceResponseJson cdsServiceResponseJson = new CdsServiceResponseJson();
|
||||
final CdsServiceResponseCardJson cdsServiceResponseCardJson = new CdsServiceResponseCardJson();
|
||||
cdsServiceResponseCardJson.setSummary("Hello Universe!");
|
||||
cdsServiceResponseCardJson.setIndicator(CdsServiceIndicatorEnum.CRITICAL);
|
||||
cdsServiceResponseCardJson.setDetail("This is a test. Do not be alarmed.");
|
||||
cdsServiceResponseCardJson.setSource(new CdsServiceResponseCardSourceJson().setLabel("World Greeter"));
|
||||
cdsServiceResponseCardJson.setExtension(theCdsServiceRequestJson.getExtension());
|
||||
cdsServiceResponseJson.addCard(cdsServiceResponseCardJson);
|
||||
return cdsServiceResponseJson;
|
||||
}
|
||||
|
||||
@CdsService(value = TEST_HOOK_PLAYBACK_ID,
|
||||
|
@ -78,21 +99,24 @@ public class HelloWorldService implements IPointcutLatch {
|
|||
prefetch = {
|
||||
@CdsServicePrefetch(value = TEST_HOOK_PREFETCH_PATIENT_KEY, query = "Patient/{{context.patientId}}"),
|
||||
@CdsServicePrefetch(value = TEST_HOOK_PREFETCH_MEDS_KEY, query = "MedicationRequest?patient={{context.patientId}}")
|
||||
})
|
||||
public String playback(CdsServiceRequestJson theCdsServiceRequestJson) {
|
||||
return "{\n" +
|
||||
" \"cards\" : [ {\n" +
|
||||
" \"summary\" : \"FhirServer: " + theCdsServiceRequestJson.getFhirServer() +
|
||||
},
|
||||
extension = """
|
||||
{
|
||||
"example-client-conformance": "http://hooks.example.org/fhir/102/Conformance/patientview"
|
||||
}
|
||||
""",
|
||||
extensionClass = ExampleConfigExtension.class)
|
||||
public CdsServiceResponseJson playback(CdsServiceRequestJson theCdsServiceRequestJson) {
|
||||
final CdsServiceResponseJson cdsServiceResponseJson = new CdsServiceResponseJson();
|
||||
final CdsServiceResponseCardJson cdsServiceResponseCardJson = new CdsServiceResponseCardJson();
|
||||
cdsServiceResponseCardJson.setSummary("FhirServer: " + theCdsServiceRequestJson.getFhirServer() +
|
||||
" Hook: " + theCdsServiceRequestJson.getHook() +
|
||||
" Hook Instance: " + theCdsServiceRequestJson.getHookInstance() +
|
||||
"\",\n" +
|
||||
" \"indicator\" : \"critical\",\n" +
|
||||
" \"source\" : {\n" +
|
||||
" \"label\" : \"World Greeter\"\n" +
|
||||
" },\n" +
|
||||
" \"detail\" : \"This is a test. Do not be alarmed.\"\n" +
|
||||
" } ]\n" +
|
||||
"}";
|
||||
" Hook Instance: " + theCdsServiceRequestJson.getHookInstance());
|
||||
cdsServiceResponseCardJson.setIndicator(CdsServiceIndicatorEnum.CRITICAL);
|
||||
cdsServiceResponseCardJson.setDetail("This is a test. Do not be alarmed.");
|
||||
cdsServiceResponseCardJson.setSource(new CdsServiceResponseCardSourceJson().setLabel("World Greeter"));
|
||||
cdsServiceResponseJson.addCard(cdsServiceResponseCardJson);
|
||||
return cdsServiceResponseJson;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.custom.extensions.model;
|
||||
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class ExampleConfigExtension extends CdsHooksExtension {
|
||||
@JsonProperty("example-client-conformance")
|
||||
String myExampleClientConformance;
|
||||
|
||||
public String getExampleClientConformance() {
|
||||
return myExampleClientConformance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.custom.extensions.model;
|
||||
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class ExampleExtension extends CdsHooksExtension {
|
||||
@JsonProperty("example-property")
|
||||
String myExampleProperty;
|
||||
|
||||
public String getExampleProperty() {
|
||||
return myExampleProperty;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.custom.extensions.model;
|
||||
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
|
||||
public class RequestExtension extends CdsHooksExtension {
|
||||
@JsonProperty("example-config-item")
|
||||
String myConfigItem;
|
||||
|
||||
public String getConfigItem() {
|
||||
return myConfigItem;
|
||||
}
|
||||
|
||||
public void setConfigItem(String theConfigItem) {
|
||||
myConfigItem = theConfigItem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.custom.extensions.model;
|
||||
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class ResponseExtension extends CdsHooksExtension {
|
||||
@JsonProperty(value = "timestamp", required = true)
|
||||
private Date myDate;
|
||||
@JsonProperty(value = "myextension-practitionerspecialty", required = true)
|
||||
private String myPractitionerSpecialty;
|
||||
|
||||
public void setTimestamp(Date theDate) {
|
||||
myDate = theDate;
|
||||
}
|
||||
|
||||
public void setPractitionerSpecialty(String thePractitionerSpecialty) {
|
||||
myPractitionerSpecialty = thePractitionerSpecialty;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.serializer;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestContextJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.custom.extensions.model.ExampleExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.svc.CdsServiceRegistryImpl;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CdsServiceRequestJsonDeserializerTest {
|
||||
@Mock
|
||||
private CdsServiceRegistryImpl myCdsServiceRegistry;
|
||||
private final FhirContext myFhirContext = FhirContext.forR4();
|
||||
private CdsServiceRequestJsonDeserializer myFixture;
|
||||
|
||||
@BeforeEach()
|
||||
void setup() {
|
||||
myFixture = new CdsServiceRequestJsonDeserializer(myCdsServiceRegistry, myFhirContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureObjectMapper() {
|
||||
// setup
|
||||
ObjectMapper input = new ObjectMapper();
|
||||
// execute
|
||||
myFixture.configureObjectMapper(input);
|
||||
// validate
|
||||
assertThat(input.getRegisteredModuleIds()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExtensionWhenClassFoundShouldDeserializeExtension() throws JsonProcessingException {
|
||||
// setup
|
||||
final String serviceId = "service-id";
|
||||
final String extension = """
|
||||
{
|
||||
"example-property": "example-value"
|
||||
}
|
||||
""";
|
||||
final CdsServiceJson cdsServiceJson = new CdsServiceJson();
|
||||
cdsServiceJson.setId(serviceId);
|
||||
cdsServiceJson.setExtensionClass(ExampleExtension.class);
|
||||
doReturn(cdsServiceJson).when(myCdsServiceRegistry).getCdsServiceJson(serviceId);
|
||||
// execute
|
||||
final ExampleExtension actual = (ExampleExtension) myFixture.deserializeExtension(serviceId, extension);
|
||||
// validate
|
||||
assertThat(actual.getExampleProperty()).isEqualTo("example-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExtensionWhenClassFoundButExtensionHasExtraPropertiesShouldIgnoreExtraProperties() throws JsonProcessingException {
|
||||
// setup
|
||||
final String serviceId = "service-id";
|
||||
final String extension = """
|
||||
{
|
||||
"example-property": "example-value",
|
||||
"example-extra-property": "example-extra-value"
|
||||
}
|
||||
""";
|
||||
final CdsServiceJson cdsServiceJson = new CdsServiceJson();
|
||||
cdsServiceJson.setId(serviceId);
|
||||
cdsServiceJson.setExtensionClass(ExampleExtension.class);
|
||||
doReturn(cdsServiceJson).when(myCdsServiceRegistry).getCdsServiceJson(serviceId);
|
||||
// execute
|
||||
final ExampleExtension actual = (ExampleExtension) myFixture.deserializeExtension(serviceId, extension);
|
||||
// validate
|
||||
assertThat(actual.getExampleProperty()).isEqualTo("example-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExtensionWhenNotClassFoundShouldReturnNull() throws JsonProcessingException {
|
||||
// setup
|
||||
final String serviceId = "service-id";
|
||||
final String extension = """
|
||||
{
|
||||
"example-property": "example-value"
|
||||
}
|
||||
""";
|
||||
final CdsServiceJson cdsServiceJson = new CdsServiceJson();
|
||||
cdsServiceJson.setId(serviceId);
|
||||
doReturn(cdsServiceJson).when(myCdsServiceRegistry).getCdsServiceJson(serviceId);
|
||||
// execute
|
||||
final CdsHooksExtension actual = myFixture.deserializeExtension(serviceId, extension);
|
||||
// validate
|
||||
assertThat(actual).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeRequestContextShouldDeserializeValidContext() throws JsonProcessingException {
|
||||
// setup
|
||||
final String encounterId = "123";
|
||||
final Patient patientContext = new Patient();
|
||||
patientContext.setId("456");
|
||||
final LinkedHashMap<String, Object> input = new LinkedHashMap<>();
|
||||
input.put("encounterId", encounterId);
|
||||
input.put("patient", patientContext);
|
||||
// execute
|
||||
final CdsServiceRequestContextJson actual = myFixture.deserializeRequestContext(input);
|
||||
// validate
|
||||
assertThat(actual.get("encounterId")).isEqualTo(encounterId);
|
||||
assertThat(actual.get("patient")).usingRecursiveComparison().isEqualTo(patientContext);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.svc;
|
||||
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsHooksExtension;
|
||||
import ca.uhn.hapi.fhir.cdshooks.custom.extensions.model.ExampleExtension;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class CdsHooksContextBooterTest {
|
||||
|
||||
|
@ -16,35 +17,34 @@ class CdsHooksContextBooterTest {
|
|||
void setUp() {
|
||||
myFixture = new CdsHooksContextBooter();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateJsonReturnsNullWhenInputIsEmptyString() {
|
||||
void serializeExtensionsReturnsNullWhenInputIsEmptyString() {
|
||||
// execute
|
||||
final String actual = myFixture.validateJson("");
|
||||
final CdsHooksExtension actual = myFixture.serializeExtensions("", ExampleExtension.class);
|
||||
// validate
|
||||
assertNull(actual);
|
||||
assertThat(actual).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateJsonThrowsExceptionWhenInputIsInvalid() {
|
||||
void serializeExtensionsThrowsExceptionWhenInputIsInvalid() {
|
||||
// setup
|
||||
final String expected = "HAPI-2378: Invalid JSON: Unrecognized token 'abc': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n" +
|
||||
" at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 4]";
|
||||
// execute
|
||||
final UnprocessableEntityException actual = assertThrows(UnprocessableEntityException.class, () -> myFixture.validateJson("abc"));
|
||||
// validate
|
||||
assertEquals(expected, actual.getMessage());
|
||||
// execute & validate
|
||||
assertThatThrownBy(
|
||||
() -> myFixture.serializeExtensions("abc", ExampleExtension.class))
|
||||
.isInstanceOf(UnprocessableEntityException.class)
|
||||
.hasMessage(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateJsonReturnsInputWhenInputIsValidJsonString() {
|
||||
void serializeExtensionsReturnsInputWhenInputIsValidJsonString() {
|
||||
// setup
|
||||
final String expected = "{\n \"com.example.timestamp\": \"2017-11-27T22:13:25Z\",\n \"myextension-practitionerspecialty\" : \"gastroenterology\"\n }";
|
||||
final String input = "{\n\"example-property\": \"some-value\" }";
|
||||
// execute
|
||||
final String actual = myFixture.validateJson(expected);
|
||||
final ExampleExtension actual = (ExampleExtension) myFixture.serializeExtensions(input, ExampleExtension.class);
|
||||
// validate
|
||||
assertEquals(expected, actual);
|
||||
assertThat(actual.getExampleProperty()).isEqualTo("some-value");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.svc;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceFeedbackJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
|
||||
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 com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CdsServiceRegistryImplTest {
|
||||
private static final String SERVICE_ID = "service-id";
|
||||
@Mock
|
||||
private CdsHooksContextBooter myCdsHooksContextBooter;
|
||||
@Mock
|
||||
private CdsPrefetchSvc myCdsPrefetchSvc;
|
||||
@Mock
|
||||
private ICdsCrServiceFactory myCdsCrServiceFactory;
|
||||
@Mock
|
||||
private ICrDiscoveryServiceFactory myCrDiscoveryServiceFactory;
|
||||
@Mock
|
||||
private CdsServiceCache myCdsServiceCache;
|
||||
private final ObjectMapper myObjectMapper = new ObjectMapper();
|
||||
private final FhirContext myFhirContext = FhirContext.forR4();
|
||||
private CdsServiceRegistryImpl myFixture;
|
||||
|
||||
@BeforeEach()
|
||||
void setup() {
|
||||
myFixture = new CdsServiceRegistryImpl(myCdsHooksContextBooter, myCdsPrefetchSvc, myObjectMapper, myCdsCrServiceFactory, myCrDiscoveryServiceFactory, myFhirContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeFeedbackResponseWhenResponseIsString() {
|
||||
// setup
|
||||
final String expectedCardText = "some-card";
|
||||
final String input = """
|
||||
{
|
||||
"card": "some-card"
|
||||
}
|
||||
""";
|
||||
// execute
|
||||
final CdsServiceFeedbackJson actual = myFixture.encodeFeedbackResponse(SERVICE_ID, input);
|
||||
// validate
|
||||
assertThat(actual.getCard()).isEqualTo(expectedCardText);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeFeedbackResponseWhenResponseIsCdsServiceFeedbackJson() {
|
||||
// setup
|
||||
final CdsServiceFeedbackJson expected = new CdsServiceFeedbackJson();
|
||||
expected.setCard("some-card");
|
||||
// execute
|
||||
final CdsServiceFeedbackJson actual = myFixture.encodeFeedbackResponse(SERVICE_ID, expected);
|
||||
// validate
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeFeedbackResponseWhenResponseIsInvalidString() {
|
||||
// setup
|
||||
final String invalidString = "some-invalid-feedback";
|
||||
// execute & validate
|
||||
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
|
||||
myFixture.encodeFeedbackResponse(SERVICE_ID, invalidString);
|
||||
}).withMessageContaining("HAPI-2538: Failed to serialize json Cds Feedback response for service service-id.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeFeedbackResponseWhenResponseIsInvalidObject() {
|
||||
// setup
|
||||
final CdsServiceResponseJson invalidObject = new CdsServiceResponseJson();
|
||||
// execute & validate
|
||||
assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> {
|
||||
myFixture.encodeFeedbackResponse(SERVICE_ID, invalidObject);
|
||||
}).withMessageContaining("HAPI-2537: Failed to cast feedback response CdsServiceFeedbackJson for service service-id.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeServiceResponseWhenResponseIsString() throws JsonProcessingException {
|
||||
// setup
|
||||
final String input = """
|
||||
{
|
||||
"cards": [
|
||||
{
|
||||
"summary": "some-summary",
|
||||
"indicator": "info",
|
||||
"source": {
|
||||
"label": "some-label"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
// execute
|
||||
final CdsServiceResponseJson actual = myFixture.encodeServiceResponse(SERVICE_ID, input);
|
||||
// validate
|
||||
assertThat(actual).usingRecursiveComparison().isEqualTo(myObjectMapper.readValue(input, CdsServiceResponseJson.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeServiceResponseWhenResponseCdsServiceResponseJson() throws JsonProcessingException {
|
||||
// setup
|
||||
final String input = """
|
||||
{
|
||||
"cards": [
|
||||
{
|
||||
"summary": "some-summary",
|
||||
"indicator": "info",
|
||||
"source": {
|
||||
"label": "some-label"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
// execute
|
||||
final CdsServiceResponseJson actual = myFixture.encodeServiceResponse(SERVICE_ID, myObjectMapper.readValue(input, CdsServiceResponseJson.class));
|
||||
// validate
|
||||
assertThat(actual).usingRecursiveComparison().isEqualTo(actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeServiceResponseWhenResponseIsInvalidString() {
|
||||
// setup
|
||||
final String invalidString = "some-string";
|
||||
// execute & validate
|
||||
assertThatExceptionOfType(ConfigurationException.class).isThrownBy(() -> {
|
||||
myFixture.encodeServiceResponse(SERVICE_ID, invalidString);
|
||||
}).withMessageContaining("Failed to json deserialize Cds service response of type java.lang.String when calling CDS Hook Service service-id.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeServiceResponseWhenResponseIsInvalidObject() {
|
||||
// setup
|
||||
final CdsServiceFeedbackJson invalidObject = new CdsServiceFeedbackJson();
|
||||
// execute & validate
|
||||
assertThatExceptionOfType(ConfigurationException.class).isThrownBy(() -> {
|
||||
myFixture.encodeServiceResponse(SERVICE_ID, invalidObject);
|
||||
}).withMessageContaining("Failed to cast Cds service response to CdsServiceResponseJson when calling CDS Hook Service service-id. The type ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceFeedbackJson cannot be casted to CdsServiceResponseJson");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCdsServiceJsonWhenServicePresent() {
|
||||
// setup
|
||||
final CdsServiceJson cdsService = new CdsServiceJson();
|
||||
cdsService.setId(SERVICE_ID);
|
||||
myFixture.setServiceCache(myCdsServiceCache);
|
||||
doReturn(cdsService).when(myCdsServiceCache).getCdsServiceJson(SERVICE_ID);
|
||||
// execute
|
||||
final CdsServiceJson actual = myFixture.getCdsServiceJson(SERVICE_ID);
|
||||
// validate
|
||||
assertThat(actual).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCdsServiceJsonWhenServiceIsNotPresent() {
|
||||
// setup
|
||||
final String serviceId = "non-existent-serviceid";
|
||||
myFixture.setServiceCache(myCdsServiceCache);
|
||||
doReturn(null).when(myCdsServiceCache).getCdsServiceJson(serviceId);
|
||||
// execute & validate
|
||||
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> {
|
||||
myFixture.getCdsServiceJson(serviceId);
|
||||
}).withMessage("HAPI-2536: No service with " + serviceId + " is registered.");
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;
|
|||
import java.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
@ -48,7 +49,7 @@ public class CdsCrServiceR4Test extends BaseCrTest {
|
|||
requestDetails.setId(planDefinitionId);
|
||||
final Parameters params = new CdsCrServiceR4(requestDetails, repository, myCdsConfigService).encodeParams(cdsServiceRequestJson);
|
||||
|
||||
assertTrue(params.getParameter().size() == 2);
|
||||
assertEquals(2, params.getParameter().size());
|
||||
assertTrue(params.getParameter("parameters").hasResource());
|
||||
}
|
||||
|
||||
|
@ -62,9 +63,9 @@ public class CdsCrServiceR4Test extends BaseCrTest {
|
|||
requestDetails.setId(planDefinitionId);
|
||||
final CdsServiceResponseJson cdsServiceResponseJson = new CdsCrServiceR4(requestDetails, repository, myCdsConfigService).encodeResponse(responseBundle);
|
||||
|
||||
assertTrue(cdsServiceResponseJson.getCards().size() == 1);
|
||||
assertTrue(!cdsServiceResponseJson.getCards().get(0).getSummary().isEmpty());
|
||||
assertTrue(!cdsServiceResponseJson.getCards().get(0).getDetail().isEmpty());
|
||||
assertEquals(1, cdsServiceResponseJson.getCards().size());
|
||||
assertFalse(cdsServiceResponseJson.getCards().get(0).getSummary().isEmpty());
|
||||
assertFalse(cdsServiceResponseJson.getCards().get(0).getDetail().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -78,7 +79,7 @@ public class CdsCrServiceR4Test extends BaseCrTest {
|
|||
requestDetails.setId(planDefinitionId);
|
||||
final CdsServiceResponseJson cdsServiceResponseJson = new CdsCrServiceR4(requestDetails, repository, myCdsConfigService).encodeResponse(responseBundle);
|
||||
|
||||
assertTrue(cdsServiceResponseJson.getServiceActions().size() == 1);
|
||||
assertEquals(1, cdsServiceResponseJson.getServiceActions().size());
|
||||
assertEquals(ActionType.CREATE, cdsServiceResponseJson.getServiceActions().get(0).getType());
|
||||
assertNotNull(cdsServiceResponseJson.getServiceActions().get(0).getResource());
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
@ -21,7 +21,7 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-caching-api</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -8,7 +8,7 @@
|
|||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<name>HAPI-FHIR</name>
|
||||
<description>An open-source implementation of the FHIR specification in Java.</description>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.8-SNAPSHOT</version>
|
||||
<version>7.3.9-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
Loading…
Reference in New Issue