From f5e94fee52bd8e8bb81c1d7f4a79768ca7f5013d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:10:00 -0700 Subject: [PATCH 001/151] Refactored validation rule engine in preparation for tranformation engine implementation --- .../etor/EtorDomainRegistration.java | 4 +- .../etor/orders/OrderController.java | 2 +- .../etor/ruleengine/Rule.java | 12 +---- .../etor/ruleengine/RuleEngine.java | 43 +++++------------ .../etor/ruleengine/RuleLoader.java | 23 +++++++--- .../etor/ruleengine/TransformationRule.java | 28 +++++++++++ .../etor/ruleengine/ValidationRule.java | 46 +++++++++---------- .../etor/ruleengine/ValidationRuleEngine.java | 18 ++++++++ .../resources/transformation_definitions.json | 9 ++++ ...tions.json => validation_definitions.json} | 0 .../etor/orders/OrderControllerTest.groovy | 2 +- .../RuleEngineIntegrationTest.groovy | 4 +- .../etor/ruleengine/RuleEngineTest.groovy | 16 +++---- .../etor/ruleengine/ValidationRuleTest.groovy | 4 +- 14 files changed, 123 insertions(+), 88 deletions(-) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java create mode 100644 etor/src/main/resources/transformation_definitions.json rename etor/src/main/resources/{rule_definitions.json => validation_definitions.json} (100%) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java index cd7486bea..9c05f95fc 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java @@ -33,8 +33,8 @@ import gov.hhs.cdc.trustedintermediary.etor.results.ResultResponse; import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender; import gov.hhs.cdc.trustedintermediary.etor.results.SendResultUseCase; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRuleEngine; import gov.hhs.cdc.trustedintermediary.external.database.DatabaseMessageLinkStorage; import gov.hhs.cdc.trustedintermediary.external.database.DatabasePartnerMetadataStorage; import gov.hhs.cdc.trustedintermediary.external.database.DbDao; @@ -133,7 +133,7 @@ public Map> domainRegistra PartnerMetadataConverter.class, HapiPartnerMetadataConverter.getInstance()); // Validation rules ApplicationContext.register(RuleLoader.class, RuleLoader.getInstance()); - ApplicationContext.register(RuleEngine.class, RuleEngine.getInstance()); + ApplicationContext.register(ValidationRuleEngine.class, ValidationRuleEngine.getInstance()); ApplicationContext.register(SendMessageHelper.class, SendMessageHelper.getInstance()); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java index 203bb6ffe..d13c1ef00 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java @@ -31,7 +31,7 @@ public static OrderController getInstance() { public Order parseOrders(DomainRequest request) throws FhirParseException { logger.logInfo("Parsing orders"); var fhirBundle = fhir.parseResource(request.getBody(), Bundle.class); - ruleEngine.validate(new HapiFhirResource(fhirBundle)); + ruleEngine.runRules(new HapiFhirResource(fhirBundle)); metadata.put(fhirBundle.getId(), EtorMetadataStep.RECEIVED_FROM_REPORT_STREAM); return new HapiOrder(fhirBundle); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java index 55146028d..1657647b1 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java @@ -12,17 +12,9 @@ public interface Rule { String getDescription(); - /** - * Descriptive message when there's a rule violation Note: When implementing this method, make - * sure that no PII or PHI is included in the message! - */ - String getViolationMessage(); - List getConditions(); - List getValidations(); - - boolean isValid(FhirResource resource); + boolean shouldRun(FhirResource resource); - boolean appliesTo(FhirResource resource); + void runRule(FhirResource resource); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 0b72ad3eb..7cd3f9a38 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -1,27 +1,17 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; -import gov.hhs.cdc.trustedintermediary.wrappers.Logger; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; /** Manages the application of rules loaded from a definitions file using the RuleLoader. */ public class RuleEngine { - - private static final RuleEngine INSTANCE = new RuleEngine(); - final List rules = new ArrayList<>(); + RuleLoader ruleLoader; + String ruleDefinitionsFileName; - @Inject Logger logger; - @Inject RuleLoader ruleLoader; - - private RuleEngine() {} - - public static RuleEngine getInstance() { - return INSTANCE; + RuleEngine(RuleLoader ruleLoader, String ruleDefinitionsFileName) { + this.ruleLoader = ruleLoader; + this.ruleDefinitionsFileName = ruleDefinitionsFileName; } public void unloadRules() { @@ -32,31 +22,22 @@ public void ensureRulesLoaded() { if (rules.isEmpty()) { synchronized (this) { if (rules.isEmpty()) { - loadRules(); + var parsedRules = ruleLoader.loadRules(ruleDefinitionsFileName); + loadRules(parsedRules); } } } } - private synchronized void loadRules() { - String fileName = "rule_definitions.json"; - try (InputStream ruleDefinitionStream = - getClass().getClassLoader().getResourceAsStream(fileName)) { - assert ruleDefinitionStream != null; - var ruleStream = - new String(ruleDefinitionStream.readAllBytes(), StandardCharsets.UTF_8); - rules.addAll(ruleLoader.loadRules(ruleStream)); - } catch (IOException | RuleLoaderException e) { - logger.logError("Failed to load rules definitions from: " + fileName, e); - } + private synchronized void loadRules(List rules) { + this.rules.addAll(rules); } - public void validate(FhirResource resource) { - logger.logDebug("Validating FHIR resource"); + public void runRules(FhirResource resource) { ensureRulesLoaded(); for (Rule rule : rules) { - if (rule.appliesTo(resource) && !rule.isValid(resource)) { - logger.logWarning("Rule violation: " + rule.getViolationMessage()); + if (rule.shouldRun(resource)) { + rule.runRule(resource); } } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index bbfa66ce1..534ef8c50 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -1,8 +1,12 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.FormatterProcessingException; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; @@ -12,6 +16,7 @@ public class RuleLoader { private static final RuleLoader INSTANCE = new RuleLoader(); @Inject Formatter formatter; + @Inject Logger logger; private RuleLoader() {} @@ -19,14 +24,18 @@ public static RuleLoader getInstance() { return INSTANCE; } - public List loadRules(String ruleStream) throws RuleLoaderException { - try { - Map> jsonObj = - formatter.convertJsonToObject(ruleStream, new TypeReference<>() {}); + public List loadRules(String fileName) { + try (InputStream ruleDefinitionStream = + getClass().getClassLoader().getResourceAsStream(fileName)) { + assert ruleDefinitionStream != null; + var rulesString = + new String(ruleDefinitionStream.readAllBytes(), StandardCharsets.UTF_8); + Map> jsonObj = + formatter.convertJsonToObject(rulesString, new TypeReference<>() {}); return jsonObj.getOrDefault("rules", Collections.emptyList()); - } catch (FormatterProcessingException e) { - throw new RuleLoaderException( - "Failed to load rules definitions for provided stream", e); + } catch (IOException | FormatterProcessingException e) { + logger.logError("Failed to load rules definitions from: " + fileName, e); + return Collections.emptyList(); } } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java new file mode 100644 index 000000000..b51ee7157 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java @@ -0,0 +1,28 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine; + +import java.util.List; + +public class TransformationRule implements Rule { + @Override + public String getName() { + return ""; + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public List getConditions() { + return List.of(); + } + + @Override + public boolean shouldRun(FhirResource resource) { + return false; + } + + @Override + public void runRule(FhirResource resource) {} +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java index ecf02cb6f..c16a159ba 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java @@ -49,7 +49,6 @@ public String getDescription() { return description; } - @Override public String getViolationMessage() { return violationMessage; } @@ -59,34 +58,12 @@ public List getConditions() { return conditions; } - @Override public List getValidations() { return validations; } @Override - public boolean isValid(FhirResource resource) { - return validations.stream() - .allMatch( - validation -> { - try { - return fhirEngine.evaluateCondition( - resource.getUnderlyingResource(), validation); - } catch (Exception e) { - logger.logError( - "Rule [" - + name - + "]: " - + "An error occurred while evaluating the validation: " - + validation, - e); - return false; - } - }); - } - - @Override - public boolean appliesTo(FhirResource resource) { + public boolean shouldRun(FhirResource resource) { return conditions.stream() .allMatch( condition -> { @@ -105,4 +82,25 @@ public boolean appliesTo(FhirResource resource) { } }); } + + @Override + public void runRule(FhirResource resource) { + for (String validation : validations) { + try { + boolean isValid = + fhirEngine.evaluateCondition(resource.getUnderlyingResource(), validation); + if (!isValid) { + logger.logWarning("Rule violation: " + violationMessage); + } + } catch (Exception e) { + logger.logError( + "Rule [" + + name + + "]: " + + "An error occurred while evaluating the validation: " + + validation, + e); + } + } + } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java new file mode 100644 index 000000000..35296a6dc --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -0,0 +1,18 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine; + +import gov.hhs.cdc.trustedintermediary.wrappers.Logger; +import javax.inject.Inject; + +public class ValidationRuleEngine extends RuleEngine { + @Inject Logger logger; + + private static final ValidationRuleEngine INSTANCE = new ValidationRuleEngine(); + + private ValidationRuleEngine() { + super(RuleLoader.getInstance(), "validation_definitions.json"); + } + + public static ValidationRuleEngine getInstance() { + return INSTANCE; + } +} diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json new file mode 100644 index 000000000..6064200aa --- /dev/null +++ b/etor/src/main/resources/transformation_definitions.json @@ -0,0 +1,9 @@ +{ + "rules": [ { + "name": "", + "description": "", + "conditions": [ ], + "transformations": [ ] + } + ] +} diff --git a/etor/src/main/resources/rule_definitions.json b/etor/src/main/resources/validation_definitions.json similarity index 100% rename from etor/src/main/resources/rule_definitions.json rename to etor/src/main/resources/validation_definitions.json diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy index 75bdc1bc1..d8a003e64 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy @@ -38,7 +38,7 @@ class OrderControllerTest extends Specification { then: actualBundle == expectedBundle - (1.._) * ruleEngine.validate(_) + (1.._) * ruleEngine.runRules(_) } def "parseOrders registers a metadata step"() { diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy index 0bc0d13dc..7629ca46a 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy @@ -41,7 +41,7 @@ class RuleEngineIntegrationTest extends Specification { def bundle = new Bundle() when: - engine.validate(new HapiFhirResource(bundle)) + engine.runRules(new HapiFhirResource(bundle)) then: (1.._) * mockLogger.logWarning(_ as String) @@ -53,7 +53,7 @@ class RuleEngineIntegrationTest extends Specification { when: exampleFhirResources.each { resource -> - engine.validate(resource) + engine.runRules(resource) } then: diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy index 1e1cf87c4..1aa9b71de 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy @@ -66,13 +66,13 @@ class RuleEngineTest extends Specification { mockRuleLoader.loadRules(_ as String) >> { throw exception } when: - ruleEngine.validate(Mock(FhirResource)) + ruleEngine.runRules(Mock(FhirResource)) then: 1 * mockLogger.logError(_ as String, exception) } - def "validate handles logging warning correctly"() { + def 'runRules handles logging warning correctly'() { given: def ruleViolationMessage = "Rule violation message" def fullRuleViolationMessage = "Rule violation: " + ruleViolationMessage @@ -82,25 +82,25 @@ class RuleEngineTest extends Specification { mockRuleLoader.loadRules(_ as String) >> [invalidRule] when: - invalidRule.appliesTo(fhirBundle) >> true + invalidRule.shouldRun(fhirBundle) >> true invalidRule.isValid(fhirBundle) >> false - ruleEngine.validate(fhirBundle) + ruleEngine.runRules(fhirBundle) then: 1 * mockLogger.logWarning(fullRuleViolationMessage) when: - invalidRule.appliesTo(fhirBundle) >> true + invalidRule.shouldRun(fhirBundle) >> true invalidRule.isValid(fhirBundle) >> true - ruleEngine.validate(fhirBundle) + ruleEngine.runRules(fhirBundle) then: 0 * mockLogger.logWarning(fullRuleViolationMessage) when: - invalidRule.appliesTo(fhirBundle) >> false + invalidRule.shouldRun(fhirBundle) >> false invalidRule.isValid(fhirBundle) >> false - ruleEngine.validate(fhirBundle) + ruleEngine.runRules(fhirBundle) then: 0 * mockLogger.logWarning(fullRuleViolationMessage) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy index 95a0b2545..7ed829be9 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy @@ -50,7 +50,7 @@ class ValidationRuleTest extends Specification { ], null) expect: - rule.appliesTo(new FhirResourceMock("resource")) == applies + rule.shouldRun(new FhirResourceMock("resource")) == applies where: conditionResult | applies @@ -67,7 +67,7 @@ class ValidationRuleTest extends Specification { def rule = new ValidationRule(null, null, null, ["condition"], null) when: - def applies = rule.appliesTo(Mock(FhirResource)) + def applies = rule.shouldRun(Mock(FhirResource)) then: 1 * mockLogger.logError(_ as String, _ as Exception) From 28505a5d895a23f4a61f062405908d08b5879a42 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:26:46 -0700 Subject: [PATCH 002/151] Fixed instantiation --- .../trustedintermediary/etor/orders/OrderController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java index d13c1ef00..4bd37e334 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java @@ -2,7 +2,7 @@ import gov.hhs.cdc.trustedintermediary.domainconnector.DomainRequest; import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRuleEngine; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrder; import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException; @@ -20,7 +20,7 @@ public class OrderController { @Inject HapiFhir fhir; @Inject Logger logger; @Inject MetricMetadata metadata; - @Inject RuleEngine ruleEngine; + @Inject ValidationRuleEngine validationEngine; private OrderController() {} @@ -31,7 +31,7 @@ public static OrderController getInstance() { public Order parseOrders(DomainRequest request) throws FhirParseException { logger.logInfo("Parsing orders"); var fhirBundle = fhir.parseResource(request.getBody(), Bundle.class); - ruleEngine.runRules(new HapiFhirResource(fhirBundle)); + validationEngine.runRules(new HapiFhirResource(fhirBundle)); metadata.put(fhirBundle.getId(), EtorMetadataStep.RECEIVED_FROM_REPORT_STREAM); return new HapiOrder(fhirBundle); } From bdd568a8eb7fb85bb4e33e2fe8e30bad34a65cbe Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:47:11 -0700 Subject: [PATCH 003/151] Refactored to make Rule interface more generic --- .../etor/ruleengine/Rule.java | 4 ++++ .../etor/ruleengine/RuleLoader.java | 2 +- .../etor/ruleengine/TransformationRule.java | 10 ++++++++++ .../etor/ruleengine/ValidationRule.java | 20 +++++++++---------- .../resources/transformation_definitions.json | 4 ++-- .../resources/validation_definitions.json | 10 +++++----- .../RuleEngineIntegrationTest.groovy | 4 ++-- .../etor/ruleengine/RuleEngineTest.groovy | 2 +- .../etor/ruleengine/RuleLoaderTest.groovy | 6 +----- .../etor/ruleengine/ValidationRuleTest.groovy | 4 ++-- 10 files changed, 38 insertions(+), 28 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java index 1657647b1..e7d27365f 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java @@ -12,8 +12,12 @@ public interface Rule { String getDescription(); + String getMessage(); + List getConditions(); + List getRules(); + boolean shouldRun(FhirResource resource); void runRule(FhirResource resource); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index 534ef8c50..0254898fe 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -32,7 +32,7 @@ public List loadRules(String fileName) { new String(ruleDefinitionStream.readAllBytes(), StandardCharsets.UTF_8); Map> jsonObj = formatter.convertJsonToObject(rulesString, new TypeReference<>() {}); - return jsonObj.getOrDefault("rules", Collections.emptyList()); + return jsonObj.getOrDefault("definitions", Collections.emptyList()); } catch (IOException | FormatterProcessingException e) { logger.logError("Failed to load rules definitions from: " + fileName, e); return Collections.emptyList(); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java index b51ee7157..499f8bddc 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java @@ -13,11 +13,21 @@ public String getDescription() { return ""; } + @Override + public String getMessage() { + return ""; + } + @Override public List getConditions() { return List.of(); } + @Override + public List getRules() { + return List.of(); + } + @Override public boolean shouldRun(FhirResource resource) { return false; diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java index c16a159ba..ec393c122 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java @@ -16,9 +16,9 @@ public class ValidationRule implements Rule { private final HapiFhir fhirEngine = ApplicationContext.getImplementation(HapiFhir.class); private String name; private String description; - private String violationMessage; + private String message; private List conditions; - private List validations; + private List rules; /** * Do not delete this constructor! It is used for JSON deserialization when loading rules from a @@ -34,9 +34,9 @@ public ValidationRule( List ruleValidations) { name = ruleName; description = ruleDescription; - violationMessage = ruleWarningMessage; + message = ruleWarningMessage; conditions = ruleConditions; - validations = ruleValidations; + rules = ruleValidations; } @Override @@ -49,8 +49,8 @@ public String getDescription() { return description; } - public String getViolationMessage() { - return violationMessage; + public String getMessage() { + return message; } @Override @@ -58,8 +58,8 @@ public List getConditions() { return conditions; } - public List getValidations() { - return validations; + public List getRules() { + return rules; } @Override @@ -85,12 +85,12 @@ public boolean shouldRun(FhirResource resource) { @Override public void runRule(FhirResource resource) { - for (String validation : validations) { + for (String validation : rules) { try { boolean isValid = fhirEngine.evaluateCondition(resource.getUnderlyingResource(), validation); if (!isValid) { - logger.logWarning("Rule violation: " + violationMessage); + logger.logWarning("Rule violation: " + message); } } catch (Exception e) { logger.logError( diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 6064200aa..3347b41e0 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -1,9 +1,9 @@ { - "rules": [ { + "definitions": [ { "name": "", "description": "", "conditions": [ ], - "transformations": [ ] + "rules": [ ] } ] } diff --git a/etor/src/main/resources/validation_definitions.json b/etor/src/main/resources/validation_definitions.json index aa828918f..f6d05cb1c 100644 --- a/etor/src/main/resources/validation_definitions.json +++ b/etor/src/main/resources/validation_definitions.json @@ -1,21 +1,21 @@ { - "rules": [ { + "definitions": [ { "name": "messageHasRequiredReceiverId", "description": "Message has required receiver id", - "violationMessage": "Message doesn't have required receiver id", + "message": "Message doesn't have required receiver id", "conditions": [ ], - "validations": [ + "rules": [ "Bundle.entry.resource.ofType(MessageHeader).destination.receiver.resolve().identifier.where(system = 'urn:ietf:rfc:3986').value.exists()" ] }, { "name": "ORMHasRequiredCardNumber", "description": "ORM has required Card Number", - "violationMessage": "ORM doesn't have required Card Number", + "message": "ORM doesn't have required Card Number", "conditions": [ "Bundle.entry.resource.ofType(MessageHeader).event.code = 'O01'" ], - "validations": [ + "rules": [ "Bundle.entry.resource.ofType(Observation).where(code.coding.code = '57723-9').value.coding.code.exists()" ] } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy index 7629ca46a..8cafcec2f 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy @@ -175,9 +175,9 @@ class RuleEngineIntegrationTest extends Specification { return new ValidationRule( name: "Rule name", description: "Rule description", - violationMessage: "Rule warning message", + message: "Rule warning message", conditions: ruleConditions, - validations: ruleValidations, + rules: ruleValidations, ) } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy index 1aa9b71de..f2194c5e7 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy @@ -78,7 +78,7 @@ class RuleEngineTest extends Specification { def fullRuleViolationMessage = "Rule violation: " + ruleViolationMessage def fhirBundle = Mock(FhirResource) def invalidRule = Mock(Rule) - invalidRule.getViolationMessage() >> ruleViolationMessage + invalidRule.getMessage() >> ruleViolationMessage mockRuleLoader.loadRules(_ as String) >> [invalidRule] when: diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy index 6f3245876..ca093b83c 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy @@ -6,10 +6,6 @@ import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import spock.lang.Specification -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths - class RuleLoaderTest extends Specification { String fileContents @@ -47,7 +43,7 @@ class RuleLoaderTest extends Specification { ValidationRule rule = rules.get(0) as ValidationRule rule.getName() == "patientName" rule.getConditions() == ["Patient.name.exists()"] - rule.getValidations() == [ + rule.getRules() == [ "Patient.name.where(use='usual').given.exists()" ] } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy index 7ed829be9..701493bea 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy @@ -33,9 +33,9 @@ class ValidationRuleTest extends Specification { then: rule.getName() == ruleName rule.getDescription() == ruleDescription - rule.getViolationMessage() == ruleWarningMessage + rule.getMessage() == ruleWarningMessage rule.getConditions() == conditions - rule.getValidations() == validations + rule.getRules() == validations } def "appliesTo returns expected boolean depending on conditions"() { From f65ceded8fc42f25ae5429a122e69eb42fa6229b Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:51:59 -0700 Subject: [PATCH 004/151] Fixed tests --- .../etor/orders/OrderControllerTest.groovy | 6 +++--- ...st.groovy => ValidationRuleEngineIntegrationTest.groovy} | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{RuleEngineIntegrationTest.groovy => ValidationRuleEngineIntegrationTest.groovy} (97%) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy index d8a003e64..034072f68 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy @@ -3,7 +3,7 @@ package gov.hhs.cdc.trustedintermediary.etor.orders import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.domainconnector.DomainRequest import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRuleEngine import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir @@ -12,14 +12,14 @@ import org.hl7.fhir.r4.model.Bundle import spock.lang.Specification class OrderControllerTest extends Specification { - def ruleEngine = Mock(RuleEngine) + def ruleEngine = Mock(ValidationRuleEngine) def setup() { TestApplicationContext.reset() TestApplicationContext.init() TestApplicationContext.register(OrderController, OrderController.getInstance()) TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) - TestApplicationContext.register(RuleEngine, ruleEngine) + TestApplicationContext.register(ValidationRuleEngine, ruleEngine) TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy similarity index 97% rename from etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy rename to etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy index 8cafcec2f..ed78ae6ce 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy @@ -17,10 +17,10 @@ import spock.lang.Specification import java.nio.file.Files import java.nio.file.Path -class RuleEngineIntegrationTest extends Specification { +class ValidationRuleEngineIntegrationTest extends Specification { def testExampleFilesPath = "../examples/Test" def fhir = HapiFhirImplementation.getInstance() - def engine = RuleEngine.getInstance() + def engine = ValidationRuleEngine.getInstance() def mockLogger = Mock(Logger) def setup() { @@ -29,7 +29,7 @@ class RuleEngineIntegrationTest extends Specification { TestApplicationContext.register(Formatter, Jackson.getInstance()) TestApplicationContext.register(HapiFhir, fhir) - TestApplicationContext.register(RuleEngine, engine) + TestApplicationContext.register(ValidationRuleEngine, engine) TestApplicationContext.register(RuleLoader, RuleLoader.getInstance()) TestApplicationContext.register(Logger, mockLogger) From 0cf56404ff0584dcd78de356ba664e96d450e8f3 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 25 Apr 2024 15:50:56 -0400 Subject: [PATCH 005/151] Update transformation_definitions.json Added dummy definitions from design discussion --- .../resources/transformation_definitions.json | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 3347b41e0..ee33f9db4 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -1,9 +1,37 @@ { "definitions": [ { - "name": "", - "description": "", - "conditions": [ ], - "rules": [ ] + "name": "flattenOBR", + "description": "Flatten OBRs", + "message": "ORM doesn't have required Card Number", + "conditions": [ + "Bundle.entry.resource.ofType(MessageHeader).destination.sender = 'CA'" + ], + "rules": [ + { + "name": "FlattenOBRTransformation", + "args": {} + }, + { + "name": "updateFhirPathValue", + "args": { + "fhirPathName": "receivingFacilityName", + "value": "CDPH" + } + }, + { + "name": "updateFhirPathValue", + "args": { + "fhirPathName": "Bundle/MSH5", + "value": "EPIC" + } + }, + { + "name": "removeFhirPathValue", + "args": { + "fhirPathName": "Bundle/PID-3.4" + } + } + ] } ] } From 86313349aa4effa6759f3bbf62fbb21928ec80c6 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:27:35 -0700 Subject: [PATCH 006/151] Added missing override tags --- .../cdc/trustedintermediary/etor/ruleengine/ValidationRule.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java index ec393c122..cc17e2746 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java @@ -49,6 +49,7 @@ public String getDescription() { return description; } + @Override public String getMessage() { return message; } @@ -58,6 +59,7 @@ public List getConditions() { return conditions; } + @Override public List getRules() { return rules; } From 6214e848cc9facb06ac05fb4c6c9fc40a90dd92a Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:34:47 -0700 Subject: [PATCH 007/151] Fixed arg naming --- .../etor/ruleengine/ValidationRule.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java index cc17e2746..74870b52f 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java @@ -29,14 +29,14 @@ public ValidationRule() {} public ValidationRule( String ruleName, String ruleDescription, - String ruleWarningMessage, + String ruleMessage, List ruleConditions, - List ruleValidations) { + List ruleActions) { name = ruleName; description = ruleDescription; - message = ruleWarningMessage; + message = ruleMessage; conditions = ruleConditions; - rules = ruleValidations; + rules = ruleActions; } @Override From 4756364b0d48b5bd13126d5bed6b9f305edf22da Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:37:49 -0700 Subject: [PATCH 008/151] Use generics to deserialize correct type of Rule implementation --- .../etor/ruleengine/RuleEngine.java | 18 +++++----- .../etor/ruleengine/RuleLoader.java | 4 +-- .../etor/ruleengine/ValidationRuleEngine.java | 9 ++--- .../resources/transformation_definitions.json | 34 ++++--------------- .../etor/ruleengine/RuleEngineTest.groovy | 7 ++-- 5 files changed, 25 insertions(+), 47 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 7cd3f9a38..daaf3b35d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -4,14 +4,16 @@ import java.util.List; /** Manages the application of rules loaded from a definitions file using the RuleLoader. */ -public class RuleEngine { - final List rules = new ArrayList<>(); - RuleLoader ruleLoader; - String ruleDefinitionsFileName; +public class RuleEngine { + final List rules = new ArrayList<>(); + private final RuleLoader ruleLoader; + private final String ruleDefinitionsFileName; + private final Class ruleClass; - RuleEngine(RuleLoader ruleLoader, String ruleDefinitionsFileName) { + RuleEngine(RuleLoader ruleLoader, String ruleDefinitionsFileName, Class ruleClass) { this.ruleLoader = ruleLoader; this.ruleDefinitionsFileName = ruleDefinitionsFileName; + this.ruleClass = ruleClass; } public void unloadRules() { @@ -22,20 +24,20 @@ public void ensureRulesLoaded() { if (rules.isEmpty()) { synchronized (this) { if (rules.isEmpty()) { - var parsedRules = ruleLoader.loadRules(ruleDefinitionsFileName); + List parsedRules = ruleLoader.loadRules(ruleDefinitionsFileName, ruleClass); loadRules(parsedRules); } } } } - private synchronized void loadRules(List rules) { + private synchronized void loadRules(List rules) { this.rules.addAll(rules); } public void runRules(FhirResource resource) { ensureRulesLoaded(); - for (Rule rule : rules) { + for (T rule : rules) { if (rule.shouldRun(resource)) { rule.runRule(resource); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index 0254898fe..dcef4b87a 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -24,13 +24,13 @@ public static RuleLoader getInstance() { return INSTANCE; } - public List loadRules(String fileName) { + public List loadRules(String fileName, Class ruleClass) { try (InputStream ruleDefinitionStream = getClass().getClassLoader().getResourceAsStream(fileName)) { assert ruleDefinitionStream != null; var rulesString = new String(ruleDefinitionStream.readAllBytes(), StandardCharsets.UTF_8); - Map> jsonObj = + Map> jsonObj = formatter.convertJsonToObject(rulesString, new TypeReference<>() {}); return jsonObj.getOrDefault("definitions", Collections.emptyList()); } catch (IOException | FormatterProcessingException e) { diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index 35296a6dc..a6def0693 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -1,15 +1,10 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; -import gov.hhs.cdc.trustedintermediary.wrappers.Logger; -import javax.inject.Inject; - -public class ValidationRuleEngine extends RuleEngine { - @Inject Logger logger; - +public class ValidationRuleEngine extends RuleEngine { private static final ValidationRuleEngine INSTANCE = new ValidationRuleEngine(); private ValidationRuleEngine() { - super(RuleLoader.getInstance(), "validation_definitions.json"); + super(RuleLoader.getInstance(), "validation_definitions.json", ValidationRule.class); } public static ValidationRuleEngine getInstance() { diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index ee33f9db4..b608d1b2c 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -1,35 +1,13 @@ { "definitions": [ { - "name": "flattenOBR", - "description": "Flatten OBRs", - "message": "ORM doesn't have required Card Number", - "conditions": [ - "Bundle.entry.resource.ofType(MessageHeader).destination.sender = 'CA'" - ], + "name": "Rule name", + "description": "Rule description", + "message": "Log message", + "conditions": [ ], "rules": [ { - "name": "FlattenOBRTransformation", - "args": {} - }, - { - "name": "updateFhirPathValue", - "args": { - "fhirPathName": "receivingFacilityName", - "value": "CDPH" - } - }, - { - "name": "updateFhirPathValue", - "args": { - "fhirPathName": "Bundle/MSH5", - "value": "EPIC" - } - }, - { - "name": "removeFhirPathValue", - "args": { - "fhirPathName": "Bundle/PID-3.4" - } + "name": "Transformation method name", + "args": { } } ] } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy index f2194c5e7..b2250dbfc 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy @@ -1,11 +1,13 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson import gov.hhs.cdc.trustedintermediary.wrappers.Logger +import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import spock.lang.Specification class RuleEngineTest extends Specification { - def ruleEngine = RuleEngine.getInstance() + def ruleEngine = ValidationRuleEngine.getInstance() def mockRuleLoader = Mock(RuleLoader) def mockLogger = Mock(Logger) @@ -16,7 +18,8 @@ class RuleEngineTest extends Specification { TestApplicationContext.init() TestApplicationContext.register(RuleLoader, mockRuleLoader) TestApplicationContext.register(Logger, mockLogger) - TestApplicationContext.register(RuleEngine, ruleEngine) + TestApplicationContext.register(ValidationRuleEngine, ruleEngine) + TestApplicationContext.register(Formatter, Jackson.getInstance()) TestApplicationContext.injectRegisteredImplementations() } From 1de28efa289b3d8ba2b61bc4f24ab44e64be5b0d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 08:16:01 -0700 Subject: [PATCH 009/151] Converted RuleEngine back into a singleton and added parameters --- .../etor/EtorDomainRegistration.java | 6 ++- .../etor/ruleengine/IRuleEngine.java | 10 +++++ .../etor/ruleengine/RuleEngine.java | 40 ++++++++++--------- .../etor/ruleengine/RuleLoader.java | 2 +- .../etor/ruleengine/ValidationRuleEngine.java | 12 +----- .../etor/ruleengine/RuleEngineTest.groovy | 20 ++++++---- 6 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/IRuleEngine.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java index c50aa3062..ded332cf8 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java @@ -33,7 +33,9 @@ import gov.hhs.cdc.trustedintermediary.etor.results.ResultResponse; import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender; import gov.hhs.cdc.trustedintermediary.etor.results.SendResultUseCase; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRule; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRuleEngine; import gov.hhs.cdc.trustedintermediary.external.database.DatabaseMessageLinkStorage; import gov.hhs.cdc.trustedintermediary.external.database.DatabasePartnerMetadataStorage; @@ -133,7 +135,9 @@ public Map> domainRegistra PartnerMetadataConverter.class, HapiPartnerMetadataConverter.getInstance()); // Validation rules ApplicationContext.register(RuleLoader.class, RuleLoader.getInstance()); - ApplicationContext.register(ValidationRuleEngine.class, ValidationRuleEngine.getInstance()); + ApplicationContext.register( + ValidationRuleEngine.class, + RuleEngine.getInstance("validation_definitions.json", ValidationRule.class)); ApplicationContext.register(SendMessageHelper.class, SendMessageHelper.getInstance()); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/IRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/IRuleEngine.java new file mode 100644 index 000000000..418460f5e --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/IRuleEngine.java @@ -0,0 +1,10 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine; + +public interface IRuleEngine { + + void unloadRules(); + + void ensureRulesLoaded(); + + void runRules(FhirResource resource); +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index daaf3b35d..d51a49157 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -2,42 +2,46 @@ import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; /** Manages the application of rules loaded from a definitions file using the RuleLoader. */ -public class RuleEngine { - final List rules = new ArrayList<>(); - private final RuleLoader ruleLoader; - private final String ruleDefinitionsFileName; - private final Class ruleClass; - - RuleEngine(RuleLoader ruleLoader, String ruleDefinitionsFileName, Class ruleClass) { - this.ruleLoader = ruleLoader; - this.ruleDefinitionsFileName = ruleDefinitionsFileName; - this.ruleClass = ruleClass; +public class RuleEngine implements IRuleEngine { + private Class ruleClass; + private String ruleDefinitionsFileName; + final List rules = new ArrayList<>(); + + private static final RuleEngine INSTANCE = new RuleEngine(); + + @Inject RuleLoader ruleLoader; + + public static RuleEngine getInstance(String ruleDefinitionsFileName, Class ruleClass) { + INSTANCE.ruleDefinitionsFileName = ruleDefinitionsFileName; + INSTANCE.ruleClass = ruleClass; + return INSTANCE; } + private RuleEngine() {} + public void unloadRules() { rules.clear(); } public void ensureRulesLoaded() { - if (rules.isEmpty()) { - synchronized (this) { - if (rules.isEmpty()) { - List parsedRules = ruleLoader.loadRules(ruleDefinitionsFileName, ruleClass); - loadRules(parsedRules); - } + synchronized (this) { + if (rules.isEmpty()) { + List parsedRules = ruleLoader.loadRules(ruleDefinitionsFileName, ruleClass); + loadRules(parsedRules); } } } - private synchronized void loadRules(List rules) { + private synchronized void loadRules(List rules) { this.rules.addAll(rules); } public void runRules(FhirResource resource) { ensureRulesLoaded(); - for (T rule : rules) { + for (Rule rule : rules) { if (rule.shouldRun(resource)) { rule.runRule(resource); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index dcef4b87a..77e4de1f4 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -24,7 +24,7 @@ public static RuleLoader getInstance() { return INSTANCE; } - public List loadRules(String fileName, Class ruleClass) { + public List loadRules(String fileName, Class ruleClass) { try (InputStream ruleDefinitionStream = getClass().getClassLoader().getResourceAsStream(fileName)) { assert ruleDefinitionStream != null; diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index a6def0693..003217165 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -1,13 +1,3 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; -public class ValidationRuleEngine extends RuleEngine { - private static final ValidationRuleEngine INSTANCE = new ValidationRuleEngine(); - - private ValidationRuleEngine() { - super(RuleLoader.getInstance(), "validation_definitions.json", ValidationRule.class); - } - - public static ValidationRuleEngine getInstance() { - return INSTANCE; - } -} +public interface ValidationRuleEngine extends IRuleEngine {} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy index b2250dbfc..951486d60 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy @@ -7,7 +7,7 @@ import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import spock.lang.Specification class RuleEngineTest extends Specification { - def ruleEngine = ValidationRuleEngine.getInstance() + def ruleEngine = RuleEngine.getInstance("validation_definitions.json", ValidationRule.class) def mockRuleLoader = Mock(RuleLoader) def mockLogger = Mock(Logger) @@ -18,18 +18,22 @@ class RuleEngineTest extends Specification { TestApplicationContext.init() TestApplicationContext.register(RuleLoader, mockRuleLoader) TestApplicationContext.register(Logger, mockLogger) - TestApplicationContext.register(ValidationRuleEngine, ruleEngine) - TestApplicationContext.register(Formatter, Jackson.getInstance()) + TestApplicationContext.register(RuleEngine, ruleEngine) TestApplicationContext.injectRegisteredImplementations() } def "ensureRulesLoaded happy path"() { + given: + def mockRuleLoader = Mock(RuleLoader) + TestApplicationContext.register(RuleLoader, mockRuleLoader) + TestApplicationContext.injectRegisteredImplementations() + when: ruleEngine.ensureRulesLoaded() then: - 1 * mockRuleLoader.loadRules(_ as String) >> [Mock(Rule)] + 1 * mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> [Mock(Rule)] ruleEngine.rules.size() == 1 } @@ -39,7 +43,7 @@ class RuleEngineTest extends Specification { ruleEngine.ensureRulesLoaded() // Call twice to test if rules are loaded only once then: - 1 * mockRuleLoader.loadRules(_ as String) >> [Mock(Rule)] + 1 * mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> [Mock(Rule)] } def "ensureRulesLoaded loads rules only once on multiple threads"() { @@ -60,13 +64,13 @@ class RuleEngineTest extends Specification { threads*.join() then: - 1 * mockRuleLoader.loadRules(_ as String) >> [Mock(Rule)] + 1 * mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> [Mock(Rule)] } def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) - mockRuleLoader.loadRules(_ as String) >> { throw exception } + mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> { throw exception } when: ruleEngine.runRules(Mock(FhirResource)) @@ -82,7 +86,7 @@ class RuleEngineTest extends Specification { def fhirBundle = Mock(FhirResource) def invalidRule = Mock(Rule) invalidRule.getMessage() >> ruleViolationMessage - mockRuleLoader.loadRules(_ as String) >> [invalidRule] + mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> [invalidRule] when: invalidRule.shouldRun(fhirBundle) >> true From 5c8073bdf9cea5fef97d0e16adda4cb09a69500e Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:07:07 -0700 Subject: [PATCH 010/151] Refactored to have a separate implementation for ValidationRuleEngine --- .../etor/EtorDomainRegistration.java | 4 +- .../etor/ruleengine/IRuleEngine.java | 10 ---- .../etor/ruleengine/RuleEngine.java | 48 ++----------------- .../etor/ruleengine/RuleLoader.java | 2 +- .../etor/ruleengine/ValidationRuleEngine.java | 47 +++++++++++++++++- ...ValidationRuleEngineIntegrationTest.groovy | 2 +- 6 files changed, 53 insertions(+), 60 deletions(-) delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/IRuleEngine.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java index ded332cf8..d839a91e3 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java @@ -33,9 +33,7 @@ import gov.hhs.cdc.trustedintermediary.etor.results.ResultResponse; import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender; import gov.hhs.cdc.trustedintermediary.etor.results.SendResultUseCase; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRule; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRuleEngine; import gov.hhs.cdc.trustedintermediary.external.database.DatabaseMessageLinkStorage; import gov.hhs.cdc.trustedintermediary.external.database.DatabasePartnerMetadataStorage; @@ -137,7 +135,7 @@ public Map> domainRegistra ApplicationContext.register(RuleLoader.class, RuleLoader.getInstance()); ApplicationContext.register( ValidationRuleEngine.class, - RuleEngine.getInstance("validation_definitions.json", ValidationRule.class)); + ValidationRuleEngine.getInstance("validation_definitions.json")); ApplicationContext.register(SendMessageHelper.class, SendMessageHelper.getInstance()); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/IRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/IRuleEngine.java deleted file mode 100644 index 418460f5e..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/IRuleEngine.java +++ /dev/null @@ -1,10 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine; - -public interface IRuleEngine { - - void unloadRules(); - - void ensureRulesLoaded(); - - void runRules(FhirResource resource); -} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index d51a49157..917acfa32 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -1,50 +1,10 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; - /** Manages the application of rules loaded from a definitions file using the RuleLoader. */ -public class RuleEngine implements IRuleEngine { - private Class ruleClass; - private String ruleDefinitionsFileName; - final List rules = new ArrayList<>(); - - private static final RuleEngine INSTANCE = new RuleEngine(); - - @Inject RuleLoader ruleLoader; - - public static RuleEngine getInstance(String ruleDefinitionsFileName, Class ruleClass) { - INSTANCE.ruleDefinitionsFileName = ruleDefinitionsFileName; - INSTANCE.ruleClass = ruleClass; - return INSTANCE; - } - - private RuleEngine() {} - - public void unloadRules() { - rules.clear(); - } - - public void ensureRulesLoaded() { - synchronized (this) { - if (rules.isEmpty()) { - List parsedRules = ruleLoader.loadRules(ruleDefinitionsFileName, ruleClass); - loadRules(parsedRules); - } - } - } +public interface RuleEngine { + void unloadRules(); - private synchronized void loadRules(List rules) { - this.rules.addAll(rules); - } + void ensureRulesLoaded(); - public void runRules(FhirResource resource) { - ensureRulesLoaded(); - for (Rule rule : rules) { - if (rule.shouldRun(resource)) { - rule.runRule(resource); - } - } - } + void runRules(FhirResource resource); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index 77e4de1f4..08c850687 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -24,7 +24,7 @@ public static RuleLoader getInstance() { return INSTANCE; } - public List loadRules(String fileName, Class ruleClass) { + public List loadRules(String fileName, Class clazz) { try (InputStream ruleDefinitionStream = getClass().getClassLoader().getResourceAsStream(fileName)) { assert ruleDefinitionStream != null; diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index 003217165..0a26acafa 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -1,3 +1,48 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; -public interface ValidationRuleEngine extends IRuleEngine {} +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; + +public class ValidationRuleEngine implements RuleEngine { + private String ruleDefinitionsFileName; + final List rules = new ArrayList<>(); + + private static final ValidationRuleEngine INSTANCE = new ValidationRuleEngine(); + + @Inject RuleLoader ruleLoader; + + public static ValidationRuleEngine getInstance(String ruleDefinitionsFileName) { + INSTANCE.ruleDefinitionsFileName = ruleDefinitionsFileName; + return INSTANCE; + } + + private ValidationRuleEngine() {} + + public void unloadRules() { + rules.clear(); + } + + public void ensureRulesLoaded() { + synchronized (this) { + if (rules.isEmpty()) { + List parsedRules = + ruleLoader.loadRules(ruleDefinitionsFileName, ValidationRule.class); + loadRules(parsedRules); + } + } + } + + private synchronized void loadRules(List rules) { + this.rules.addAll(rules); + } + + public void runRules(FhirResource resource) { + ensureRulesLoaded(); + for (ValidationRule rule : rules) { + if (rule.shouldRun(resource)) { + rule.runRule(resource); + } + } + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy index ed78ae6ce..00a47f87f 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy @@ -20,7 +20,7 @@ import java.nio.file.Path class ValidationRuleEngineIntegrationTest extends Specification { def testExampleFilesPath = "../examples/Test" def fhir = HapiFhirImplementation.getInstance() - def engine = ValidationRuleEngine.getInstance() + def engine = ValidationRuleEngine.getInstance("validation_definitions.json") def mockLogger = Mock(Logger) def setup() { From 45a3bca1574a6aa8827811f671d1cf95950fce1b Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:36:24 -0700 Subject: [PATCH 011/151] Refactored to pass TypeReference and fix mapping issue --- .../cdc/trustedintermediary/etor/ruleengine/RuleLoader.java | 5 +++-- .../etor/ruleengine/ValidationRuleEngine.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index 08c850687..7745dc95c 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -24,14 +24,15 @@ public static RuleLoader getInstance() { return INSTANCE; } - public List loadRules(String fileName, Class clazz) { + public List loadRules( + String fileName, TypeReference>> typeReference) { try (InputStream ruleDefinitionStream = getClass().getClassLoader().getResourceAsStream(fileName)) { assert ruleDefinitionStream != null; var rulesString = new String(ruleDefinitionStream.readAllBytes(), StandardCharsets.UTF_8); Map> jsonObj = - formatter.convertJsonToObject(rulesString, new TypeReference<>() {}); + formatter.convertJsonToObject(rulesString, typeReference); return jsonObj.getOrDefault("definitions", Collections.emptyList()); } catch (IOException | FormatterProcessingException e) { logger.logError("Failed to load rules definitions from: " + fileName, e); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index 0a26acafa..e505e3c22 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -27,7 +28,7 @@ public void ensureRulesLoaded() { synchronized (this) { if (rules.isEmpty()) { List parsedRules = - ruleLoader.loadRules(ruleDefinitionsFileName, ValidationRule.class); + ruleLoader.loadRules(ruleDefinitionsFileName, new TypeReference<>() {}); loadRules(parsedRules); } } From d5490e89b4422da1ac5772d7bb7dbc0940f50491 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:49:58 -0700 Subject: [PATCH 012/151] Added missing override tags --- .../etor/ruleengine/ValidationRuleEngine.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index e505e3c22..1f2d77c45 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -20,10 +20,12 @@ public static ValidationRuleEngine getInstance(String ruleDefinitionsFileName) { private ValidationRuleEngine() {} + @Override public void unloadRules() { rules.clear(); } + @Override public void ensureRulesLoaded() { synchronized (this) { if (rules.isEmpty()) { @@ -34,10 +36,7 @@ public void ensureRulesLoaded() { } } - private synchronized void loadRules(List rules) { - this.rules.addAll(rules); - } - + @Override public void runRules(FhirResource resource) { ensureRulesLoaded(); for (ValidationRule rule : rules) { @@ -46,4 +45,8 @@ public void runRules(FhirResource resource) { } } } + + private synchronized void loadRules(List rules) { + this.rules.addAll(rules); + } } From 8e7398c77899f966b49a86910b6feafcb7e81d6b Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:12:54 -0700 Subject: [PATCH 013/151] Fixed tests in ValidationRuleEngineIntegrationTest --- ...ValidationRuleEngineIntegrationTest.groovy | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy index 00a47f87f..52a91350a 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy @@ -64,22 +64,25 @@ class ValidationRuleEngineIntegrationTest extends Specification { given: def fhirResource = getExampleFhirResource("e2e/orders/001_OML_O21_short.fhir") def validation = "Bundle.entry.resource.ofType(MessageHeader).focus.resolve().category.exists()" - def rule = createValidationRule([], [validation]) + Rule rule = createValidationRule([], [validation]) when: - def applies = rule.isValid(fhirResource) + rule.runRule(fhirResource) then: - applies + 0 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Throwable) } def "validation rules pass for test files"() { given: def fhirResource = getExampleFhirResource(testFile) def rule = createValidationRule([], [validation]) + 0 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Throwable) expect: - rule.isValid(fhirResource) + rule.runRule(fhirResource) where: testFile | validation @@ -93,9 +96,11 @@ class ValidationRuleEngineIntegrationTest extends Specification { given: def fhirResource = getExampleFhirResource(testFile) def rule = createValidationRule([], [validation]) + 1 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Throwable) expect: - !rule.isValid(fhirResource) + rule.runRule(fhirResource) where: testFile | validation @@ -118,9 +123,11 @@ class ValidationRuleEngineIntegrationTest extends Specification { def bundle = createMessageBundle(receiverOrganization: receiverOrganization) // for some reason, we need to encode and decode the bundle for resolve() to work def fhirResource = new HapiFhirResource(fhir.parseResource(fhir.encodeResourceToJson(bundle), Bundle)) + rule.runRule(fhirResource) then: - rule.isValid(fhirResource) + 0 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Throwable) when: receiverOrganization = new Organization() @@ -131,9 +138,11 @@ class ValidationRuleEngineIntegrationTest extends Specification { .setValue("simulated-hospital-id") bundle = createMessageBundle(receiverOrganization: receiverOrganization) fhirResource = new HapiFhirResource(fhir.parseResource(fhir.encodeResourceToJson(bundle), Bundle)) + rule.runRule(fhirResource) then: - !rule.isValid(fhirResource) + 1 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Throwable) when: receiverOrganization = new Organization() @@ -143,9 +152,11 @@ class ValidationRuleEngineIntegrationTest extends Specification { .setValue("simulated-hospital-id") bundle = createMessageBundle(receiverOrganization: receiverOrganization) fhirResource = new HapiFhirResource(fhir.parseResource(fhir.encodeResourceToJson(bundle), Bundle)) + rule.runRule(fhirResource) then: - !rule.isValid(fhirResource) + 1 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Throwable) } Bundle createMessageBundle(Map params) { From dbd80438ef865af8c3c459e85b88392a92284b08 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:24:22 -0700 Subject: [PATCH 014/151] Fixed more tests --- ...ValidationRuleEngineIntegrationTest.groovy | 12 ++++----- ...groovy => ValidationRuleEngineTest.groovy} | 13 +++++----- .../etor/ruleengine/ValidationRuleTest.groovy | 25 ++++++++++++------- 3 files changed, 28 insertions(+), 22 deletions(-) rename etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{RuleEngineTest.groovy => ValidationRuleEngineTest.groovy} (86%) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy index 52a91350a..3cef1191e 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy @@ -71,7 +71,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logWarning(_ as String) - 0 * mockLogger.logError(_ as String, _ as Throwable) + 0 * mockLogger.logError(_ as String, _ as Exception) } def "validation rules pass for test files"() { @@ -79,7 +79,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { def fhirResource = getExampleFhirResource(testFile) def rule = createValidationRule([], [validation]) 0 * mockLogger.logWarning(_ as String) - 0 * mockLogger.logError(_ as String, _ as Throwable) + 0 * mockLogger.logError(_ as String, _ as Exception) expect: rule.runRule(fhirResource) @@ -97,7 +97,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { def fhirResource = getExampleFhirResource(testFile) def rule = createValidationRule([], [validation]) 1 * mockLogger.logWarning(_ as String) - 0 * mockLogger.logError(_ as String, _ as Throwable) + 0 * mockLogger.logError(_ as String, _ as Exception) expect: rule.runRule(fhirResource) @@ -127,7 +127,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logWarning(_ as String) - 0 * mockLogger.logError(_ as String, _ as Throwable) + 0 * mockLogger.logError(_ as String, _ as Exception) when: receiverOrganization = new Organization() @@ -142,7 +142,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { then: 1 * mockLogger.logWarning(_ as String) - 0 * mockLogger.logError(_ as String, _ as Throwable) + 0 * mockLogger.logError(_ as String, _ as Exception) when: receiverOrganization = new Organization() @@ -156,7 +156,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { then: 1 * mockLogger.logWarning(_ as String) - 0 * mockLogger.logError(_ as String, _ as Throwable) + 0 * mockLogger.logError(_ as String, _ as Exception) } Bundle createMessageBundle(Map params) { diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy similarity index 86% rename from etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy rename to etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index 951486d60..cf20bec19 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -1,13 +1,12 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson import gov.hhs.cdc.trustedintermediary.wrappers.Logger -import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter +import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference import spock.lang.Specification -class RuleEngineTest extends Specification { - def ruleEngine = RuleEngine.getInstance("validation_definitions.json", ValidationRule.class) +class ValidationRuleEngineTest extends Specification { + def ruleEngine = ValidationRuleEngine.getInstance("validation_definitions.json") def mockRuleLoader = Mock(RuleLoader) def mockLogger = Mock(Logger) @@ -70,7 +69,7 @@ class RuleEngineTest extends Specification { def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) - mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> { throw exception } + mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> { throw exception } when: ruleEngine.runRules(Mock(FhirResource)) @@ -79,14 +78,14 @@ class RuleEngineTest extends Specification { 1 * mockLogger.logError(_ as String, exception) } - def 'runRules handles logging warning correctly'() { + def "runRules handles logging warning correctly"() { given: def ruleViolationMessage = "Rule violation message" def fullRuleViolationMessage = "Rule violation: " + ruleViolationMessage def fhirBundle = Mock(FhirResource) def invalidRule = Mock(Rule) invalidRule.getMessage() >> ruleViolationMessage - mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> [invalidRule] + mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [invalidRule] when: invalidRule.shouldRun(fhirBundle) >> true diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy index 701493bea..68883c1cf 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy @@ -77,7 +77,6 @@ class ValidationRuleTest extends Specification { def "isValid returns expected boolean depending on validations"() { given: def mockFhir = Mock(HapiFhir) - mockFhir.evaluateCondition(_ as Object, _ as String) >> true >> validationResult TestApplicationContext.register(HapiFhir, mockFhir) def rule = new ValidationRule(null, null, null, null, [ @@ -85,13 +84,21 @@ class ValidationRuleTest extends Specification { "secondValidation" ]) - expect: - rule.isValid(new FhirResourceMock("resource")) == valid + when: + mockFhir.evaluateCondition(_ as Object, _ as String) >> true >> true + rule.runRule(new FhirResourceMock("resource")) - where: - validationResult | valid - true | true - false | false + then: + 0 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Exception) + + when: + mockFhir.evaluateCondition(_ as Object, _ as String) >> true >> false + rule.runRule(new FhirResourceMock("resource")) + + then: + 1 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Exception) } def "isValid logs an error and returns false if an exception happens when evaluating a validation"() { @@ -103,10 +110,10 @@ class ValidationRuleTest extends Specification { def rule = new ValidationRule(null, null, null, null, ["validation"]) when: - def valid = rule.isValid(Mock(FhirResource)) + rule.runRule(Mock(FhirResource)) then: + 0 * mockLogger.logWarning(_ as String) 1 * mockLogger.logError(_ as String, _ as Exception) - !valid } } From d399054b91b2ddfc730e964dbe8a6df4287d927d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:56:47 -0700 Subject: [PATCH 015/151] Removed transformation engine implementation to move into new PR --- .../etor/ruleengine/TransformationRule.java | 38 ------------------- .../resources/transformation_definitions.json | 15 -------- 2 files changed, 53 deletions(-) delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java delete mode 100644 etor/src/main/resources/transformation_definitions.json diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java deleted file mode 100644 index 499f8bddc..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java +++ /dev/null @@ -1,38 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine; - -import java.util.List; - -public class TransformationRule implements Rule { - @Override - public String getName() { - return ""; - } - - @Override - public String getDescription() { - return ""; - } - - @Override - public String getMessage() { - return ""; - } - - @Override - public List getConditions() { - return List.of(); - } - - @Override - public List getRules() { - return List.of(); - } - - @Override - public boolean shouldRun(FhirResource resource) { - return false; - } - - @Override - public void runRule(FhirResource resource) {} -} diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json deleted file mode 100644 index b608d1b2c..000000000 --- a/etor/src/main/resources/transformation_definitions.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "definitions": [ { - "name": "Rule name", - "description": "Rule description", - "message": "Log message", - "conditions": [ ], - "rules": [ - { - "name": "Transformation method name", - "args": { } - } - ] - } - ] -} From 24ae3d3ca11682b33fb1475cdc300d0636072032 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:01:10 -0700 Subject: [PATCH 016/151] Added TransformationRuleEngine skeleton --- .../etor/ruleengine/TransformationRuleEngine.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngine.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngine.java new file mode 100644 index 000000000..41806478f --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngine.java @@ -0,0 +1,12 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine; + +public class TransformationRuleEngine implements RuleEngine { + @Override + public void unloadRules() {} + + @Override + public void ensureRulesLoaded() {} + + @Override + public void runRules(FhirResource resource) {} +} From 0eb2682643fdfecc388b72ea1d1483a4104cf8b3 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:03:44 -0700 Subject: [PATCH 017/151] Moved transformation engine files from refactoring PR --- .../etor/ruleengine/TransformationRule.java | 38 +++++++++++++++++++ .../resources/transformation_definitions.json | 15 ++++++++ 2 files changed, 53 insertions(+) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java create mode 100644 etor/src/main/resources/transformation_definitions.json diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java new file mode 100644 index 000000000..499f8bddc --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java @@ -0,0 +1,38 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine; + +import java.util.List; + +public class TransformationRule implements Rule { + @Override + public String getName() { + return ""; + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public List getConditions() { + return List.of(); + } + + @Override + public List getRules() { + return List.of(); + } + + @Override + public boolean shouldRun(FhirResource resource) { + return false; + } + + @Override + public void runRule(FhirResource resource) {} +} diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json new file mode 100644 index 000000000..b608d1b2c --- /dev/null +++ b/etor/src/main/resources/transformation_definitions.json @@ -0,0 +1,15 @@ +{ + "definitions": [ { + "name": "Rule name", + "description": "Rule description", + "message": "Log message", + "conditions": [ ], + "rules": [ + { + "name": "Transformation method name", + "args": { } + } + ] + } + ] +} From a865999165069bb04dbf602ce2e888eec5177f91 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 08:34:46 -0700 Subject: [PATCH 018/151] Added actual transformation rules based on existing transformations --- .../resources/transformation_definitions.json | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index b608d1b2c..39cc9c043 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -1,12 +1,40 @@ { "definitions": [ { - "name": "Rule name", - "description": "Rule description", - "message": "Log message", + "name": "addEtorProcessingTag", + "description": "Add ETOR Processing Tag", + "message": "", "conditions": [ ], "rules": [ { - "name": "Transformation method name", + "name": "addEtorTagToBundle", + "args": { } + } + ] + }, + { + "name": "convertToOmlOrder", + "description": "Convert to OML Order", + "message": "", + "conditions": [ + "Bundle.entry.resource.ofType(MessageHeader).event.code = 'O01'" + ], + "rules": [ + { + "name": "convertToOmlOrder", + "args": { } + } + ] + }, + { + "name": "addContactSectionToPatientResource", + "description": "Add Contact Section to Patient Resource", + "message": "", + "conditions": [ + "Bundle.entry.resource.ofType(MessageHeader).event.code = 'O01'" + ], + "rules": [ + { + "name": "addContactSectionToPatientResource", "args": { } } ] From 76b7d82e14f57f399d7eddecafb88adb4621215b Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 12:03:52 -0400 Subject: [PATCH 019/151] Update ValidationRuleTest.groovy Fix method names --- .../etor/ruleengine/ValidationRuleTest.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy index 68883c1cf..623c6a3cd 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy @@ -38,7 +38,7 @@ class ValidationRuleTest extends Specification { rule.getRules() == validations } - def "appliesTo returns expected boolean depending on conditions"() { + def "shouldRun returns expected boolean depending on conditions"() { given: def mockFhir = Mock(HapiFhir) mockFhir.evaluateCondition(_ as Object, _ as String) >> true >> conditionResult @@ -58,7 +58,7 @@ class ValidationRuleTest extends Specification { false | false } - def "appliesTo logs an error and returns false if an exception happens when evaluating a condition"() { + def "shouldRun logs an error and returns false if an exception happens when evaluating a condition"() { given: def mockFhir = Mock(HapiFhirImplementation) mockFhir.evaluateCondition(_ as Object, "condition") >> { throw new Exception() } @@ -74,7 +74,7 @@ class ValidationRuleTest extends Specification { !applies } - def "isValid returns expected boolean depending on validations"() { + def "runRule returns expected boolean depending on validations"() { given: def mockFhir = Mock(HapiFhir) TestApplicationContext.register(HapiFhir, mockFhir) @@ -101,7 +101,7 @@ class ValidationRuleTest extends Specification { 0 * mockLogger.logError(_ as String, _ as Exception) } - def "isValid logs an error and returns false if an exception happens when evaluating a validation"() { + def "runRule logs an error and returns false if an exception happens when evaluating a validation"() { given: def mockFhir = Mock(HapiFhirImplementation) mockFhir.evaluateCondition(_ as Object, "condition") >> { throw new Exception() } From 27ea75d5ca0324104c729ef199f99750b8871230 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 12:04:05 -0400 Subject: [PATCH 020/151] Update ValidationRuleEngineTest.groovy Updated one of the failing tests --- .../etor/ruleengine/ValidationRuleEngineTest.groovy | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index cf20bec19..fe981c160 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -83,21 +83,23 @@ class ValidationRuleEngineTest extends Specification { def ruleViolationMessage = "Rule violation message" def fullRuleViolationMessage = "Rule violation: " + ruleViolationMessage def fhirBundle = Mock(FhirResource) - def invalidRule = Mock(Rule) + def invalidRule = Mock(ValidationRule) invalidRule.getMessage() >> ruleViolationMessage + invalidRule.shouldRun(fhirBundle) >> true mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [invalidRule] when: - invalidRule.shouldRun(fhirBundle) >> true - invalidRule.isValid(fhirBundle) >> false + invalidRule.runRule(fhirBundle) >> { + mockLogger.logWarning(fullRuleViolationMessage) + } ruleEngine.runRules(fhirBundle) then: 1 * mockLogger.logWarning(fullRuleViolationMessage) when: + invalidRule.runRule(fhirBundle) >> null invalidRule.shouldRun(fhirBundle) >> true - invalidRule.isValid(fhirBundle) >> true ruleEngine.runRules(fhirBundle) then: @@ -105,7 +107,6 @@ class ValidationRuleEngineTest extends Specification { when: invalidRule.shouldRun(fhirBundle) >> false - invalidRule.isValid(fhirBundle) >> false ruleEngine.runRules(fhirBundle) then: From 0f1b27513ba3595715013e2013afcc6f67e3994a Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 12:46:48 -0400 Subject: [PATCH 021/151] Update ValidationRuleEngine.java Use double checked locking on ensureRulesloaded --- .../etor/ruleengine/ValidationRuleEngine.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index 1f2d77c45..3b59e5994 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -27,11 +27,13 @@ public void unloadRules() { @Override public void ensureRulesLoaded() { - synchronized (this) { - if (rules.isEmpty()) { - List parsedRules = - ruleLoader.loadRules(ruleDefinitionsFileName, new TypeReference<>() {}); - loadRules(parsedRules); + if (rules.isEmpty()) { + synchronized (this) { + if (rules.isEmpty()) { + List parsedRules = + ruleLoader.loadRules(ruleDefinitionsFileName, new TypeReference<>() {}); + loadRules(parsedRules); + } } } } From 5acab7ae321e88651bd05da7ee4cd9590af840f2 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 12:47:20 -0400 Subject: [PATCH 022/151] Update ValidationRuleEngineTest.groovy Update more test cases --- .../ruleengine/ValidationRuleEngineTest.groovy | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index fe981c160..0eac4cf5d 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -37,20 +37,25 @@ class ValidationRuleEngineTest extends Specification { } def "ensureRulesLoaded loads rules only once by default"() { + given: + mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [] + when: ruleEngine.ensureRulesLoaded() ruleEngine.ensureRulesLoaded() // Call twice to test if rules are loaded only once then: - 1 * mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> [Mock(Rule)] + 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) } def "ensureRulesLoaded loads rules only once on multiple threads"() { given: def threadsNum = 10 def iterations = 4 + def mockRule = Mock(ValidationRule) when: + mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] List threads = [] (1..threadsNum).each { threadId -> threads.add(new Thread({ @@ -63,13 +68,16 @@ class ValidationRuleEngineTest extends Specification { threads*.join() then: - 1 * mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> [Mock(Rule)] + 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) } def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> { throw exception } + mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> { + mockLogger.logError("Error loading rules", exception) + return [] + } when: ruleEngine.runRules(Mock(FhirResource)) From 28b0d7afc7015441aa609b86e82ee74f8b95e98b Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 13:04:23 -0400 Subject: [PATCH 023/151] Update ValidationRuleEngineTest.groovy All tests pass --- .../etor/ruleengine/ValidationRuleEngineTest.groovy | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index 0eac4cf5d..b3e84a546 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -9,6 +9,7 @@ class ValidationRuleEngineTest extends Specification { def ruleEngine = ValidationRuleEngine.getInstance("validation_definitions.json") def mockRuleLoader = Mock(RuleLoader) def mockLogger = Mock(Logger) + def mockRule = Mock(ValidationRule) def setup() { ruleEngine.unloadRules() @@ -24,35 +25,33 @@ class ValidationRuleEngineTest extends Specification { def "ensureRulesLoaded happy path"() { given: - def mockRuleLoader = Mock(RuleLoader) - TestApplicationContext.register(RuleLoader, mockRuleLoader) - TestApplicationContext.injectRegisteredImplementations() + mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] when: ruleEngine.ensureRulesLoaded() then: - 1 * mockRuleLoader.loadRules(_ as String, ValidationRule.class) >> [Mock(Rule)] + 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] ruleEngine.rules.size() == 1 } def "ensureRulesLoaded loads rules only once by default"() { given: - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [] + mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] when: ruleEngine.ensureRulesLoaded() ruleEngine.ensureRulesLoaded() // Call twice to test if rules are loaded only once then: - 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) + 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + ruleEngine.rules.size() == 1 } def "ensureRulesLoaded loads rules only once on multiple threads"() { given: def threadsNum = 10 def iterations = 4 - def mockRule = Mock(ValidationRule) when: mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] From 1f57bb1b02f7c3bb2978abcb6c843eacc3d3b4ef Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Mon, 29 Apr 2024 12:28:03 -0500 Subject: [PATCH 024/151] Update Rule loading to handle Path for flexibility --- .../etor/ruleengine/RuleLoader.java | 10 +++--- .../etor/ruleengine/ValidationRuleEngine.java | 11 +++++- .../etor/ruleengine/RuleLoaderTest.groovy | 35 +++++++++++++------ 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index 7745dc95c..e5cfa7112 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -7,6 +7,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Map; @@ -24,10 +26,8 @@ public static RuleLoader getInstance() { return INSTANCE; } - public List loadRules( - String fileName, TypeReference>> typeReference) { - try (InputStream ruleDefinitionStream = - getClass().getClassLoader().getResourceAsStream(fileName)) { + public List loadRules(Path path, TypeReference>> typeReference) { + try (InputStream ruleDefinitionStream = Files.newInputStream(path)) { assert ruleDefinitionStream != null; var rulesString = new String(ruleDefinitionStream.readAllBytes(), StandardCharsets.UTF_8); @@ -35,7 +35,7 @@ public List loadRules( formatter.convertJsonToObject(rulesString, typeReference); return jsonObj.getOrDefault("definitions", Collections.emptyList()); } catch (IOException | FormatterProcessingException e) { - logger.logError("Failed to load rules definitions from: " + fileName, e); + logger.logError("Failed to load rules definitions from: " + path.getFileName(), e); return Collections.emptyList(); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index 1f2d77c45..bd934718c 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -1,6 +1,8 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -29,8 +31,15 @@ public void unloadRules() { public void ensureRulesLoaded() { synchronized (this) { if (rules.isEmpty()) { + Path path = + Paths.get( + getClass() + .getClassLoader() + .getResource(ruleDefinitionsFileName) + .getPath()); + List parsedRules = - ruleLoader.loadRules(ruleDefinitionsFileName, new TypeReference<>() {}); + ruleLoader.loadRules(path, new TypeReference<>() {}); loadRules(parsedRules); } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy index ca093b83c..6887b46a0 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy @@ -4,11 +4,16 @@ import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter +import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference import spock.lang.Specification +import java.nio.file.Files +import java.nio.file.Path + class RuleLoaderTest extends Specification { String fileContents + Path tempFile def setup() { TestApplicationContext.reset() @@ -16,17 +21,25 @@ class RuleLoaderTest extends Specification { TestApplicationContext.register(RuleLoader, RuleLoader.getInstance()) TestApplicationContext.injectRegisteredImplementations() + tempFile = Files.createTempFile("test_validation_definition", ".json") fileContents = """ - { - "rules": [ + { + "definitions": [ { - "name": "patientName", - "conditions": ["Patient.name.exists()"], - "validations": ["Patient.name.where(use='usual').given.exists()"] + "name": "patientName", + "description": "a test rule", + "message": "testing the message", + "conditions": ["Patient.name.exists()"], + "rules": ["Patient.name.where(use='usual').given.exists()"] } - ] - } - """ + ] + } + """ + Files.writeString(tempFile, fileContents) + } + + def cleanup(){ + Files.deleteIfExists(tempFile) } def "load rules from file"() { @@ -36,19 +49,21 @@ class RuleLoaderTest extends Specification { TestApplicationContext.injectRegisteredImplementations() when: - List rules = RuleLoader.getInstance().loadRules(fileContents) + List rules = RuleLoader.getInstance().loadRules(tempFile, new TypeReference>>() {}) then: rules.size() == 1 ValidationRule rule = rules.get(0) as ValidationRule rule.getName() == "patientName" + rule.getDescription() == "a test rule" + rule.getMessage() == "testing the message" rule.getConditions() == ["Patient.name.exists()"] rule.getRules() == [ "Patient.name.where(use='usual').given.exists()" ] } - def "handle FormatterProcessingException when loading rules from file"() { + def "handle FormatterProcessingException when loading rules from a non existent file"() { when: RuleLoader.getInstance().loadRules("!K@WJ#8uhy") From 34a2bf9c40b2d647e6bf80cfaf1f37243bc44219 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:57:05 -0700 Subject: [PATCH 025/151] Fixed remaining test --- .../etor/ruleengine/RuleEngine.java | 2 +- .../etor/ruleengine/RuleLoader.java | 8 +++--- .../etor/ruleengine/ValidationRuleEngine.java | 11 ++++++-- .../etor/ruleengine/RuleLoaderTest.groovy | 26 ++++++++++--------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 917acfa32..7c4ffb11e 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -4,7 +4,7 @@ public interface RuleEngine { void unloadRules(); - void ensureRulesLoaded(); + void ensureRulesLoaded() throws RuleLoaderException; void runRules(FhirResource resource); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index e5cfa7112..7992a01d6 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -26,17 +26,17 @@ public static RuleLoader getInstance() { return INSTANCE; } - public List loadRules(Path path, TypeReference>> typeReference) { + public List loadRules(Path path, TypeReference>> typeReference) + throws RuleLoaderException { try (InputStream ruleDefinitionStream = Files.newInputStream(path)) { - assert ruleDefinitionStream != null; var rulesString = new String(ruleDefinitionStream.readAllBytes(), StandardCharsets.UTF_8); Map> jsonObj = formatter.convertJsonToObject(rulesString, typeReference); return jsonObj.getOrDefault("definitions", Collections.emptyList()); } catch (IOException | FormatterProcessingException e) { - logger.logError("Failed to load rules definitions from: " + path.getFileName(), e); - return Collections.emptyList(); + throw new RuleLoaderException( + "Failed to load rules definitions for provided path: " + path, e); } } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index 474b83ba9..523aa674a 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,6 +14,7 @@ public class ValidationRuleEngine implements RuleEngine { private static final ValidationRuleEngine INSTANCE = new ValidationRuleEngine(); + @Inject Logger logger; @Inject RuleLoader ruleLoader; public static ValidationRuleEngine getInstance(String ruleDefinitionsFileName) { @@ -28,7 +30,7 @@ public void unloadRules() { } @Override - public void ensureRulesLoaded() { + public void ensureRulesLoaded() throws RuleLoaderException { if (rules.isEmpty()) { synchronized (this) { if (rules.isEmpty()) { @@ -49,7 +51,12 @@ public void ensureRulesLoaded() { @Override public void runRules(FhirResource resource) { - ensureRulesLoaded(); + try { + ensureRulesLoaded(); + } catch (RuleLoaderException e) { + logger.logError("Failed to load rules definitions", e); + return; + } for (ValidationRule rule : rules) { if (rule.shouldRun(resource)) { rule.runRule(resource); diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy index 6887b46a0..38f31ab70 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy @@ -19,9 +19,19 @@ class RuleLoaderTest extends Specification { TestApplicationContext.reset() TestApplicationContext.init() TestApplicationContext.register(RuleLoader, RuleLoader.getInstance()) + TestApplicationContext.register(Formatter, Jackson.getInstance()) + TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) TestApplicationContext.injectRegisteredImplementations() tempFile = Files.createTempFile("test_validation_definition", ".json") + } + + def cleanup(){ + Files.deleteIfExists(tempFile) + } + + def "load rules from file"() { + given: fileContents = """ { "definitions": [ @@ -36,17 +46,6 @@ class RuleLoaderTest extends Specification { } """ Files.writeString(tempFile, fileContents) - } - - def cleanup(){ - Files.deleteIfExists(tempFile) - } - - def "load rules from file"() { - given: - TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) - TestApplicationContext.register(Formatter, Jackson.getInstance()) - TestApplicationContext.injectRegisteredImplementations() when: List rules = RuleLoader.getInstance().loadRules(tempFile, new TypeReference>>() {}) @@ -64,8 +63,11 @@ class RuleLoaderTest extends Specification { } def "handle FormatterProcessingException when loading rules from a non existent file"() { + given: + Files.writeString(tempFile, "!K@WJ#8uhy") + when: - RuleLoader.getInstance().loadRules("!K@WJ#8uhy") + RuleLoader.getInstance().loadRules(tempFile, new TypeReference>>() {}) then: thrown(RuleLoaderException) From e137c1dce7a916caf549f8dcbfc74b4a814cab3d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:12:34 -0700 Subject: [PATCH 026/151] Fixed actual remaining test --- .../ValidationRuleEngineTest.groovy | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index b3e84a546..5423d4e85 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -5,6 +5,8 @@ import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference import spock.lang.Specification +import java.nio.file.Path + class ValidationRuleEngineTest extends Specification { def ruleEngine = ValidationRuleEngine.getInstance("validation_definitions.json") def mockRuleLoader = Mock(RuleLoader) @@ -25,26 +27,26 @@ class ValidationRuleEngineTest extends Specification { def "ensureRulesLoaded happy path"() { given: - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] when: ruleEngine.ensureRulesLoaded() then: - 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] ruleEngine.rules.size() == 1 } def "ensureRulesLoaded loads rules only once by default"() { given: - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] when: ruleEngine.ensureRulesLoaded() ruleEngine.ensureRulesLoaded() // Call twice to test if rules are loaded only once then: - 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] ruleEngine.rules.size() == 1 } @@ -54,7 +56,7 @@ class ValidationRuleEngineTest extends Specification { def iterations = 4 when: - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] List threads = [] (1..threadsNum).each { threadId -> threads.add(new Thread({ @@ -67,13 +69,13 @@ class ValidationRuleEngineTest extends Specification { threads*.join() then: - 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) + 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) } def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> { + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> { mockLogger.logError("Error loading rules", exception) return [] } @@ -88,12 +90,12 @@ class ValidationRuleEngineTest extends Specification { def "runRules handles logging warning correctly"() { given: def ruleViolationMessage = "Rule violation message" - def fullRuleViolationMessage = "Rule violation: " + ruleViolationMessage + def fullRuleViolationMessage = "Validation failed: " + ruleViolationMessage def fhirBundle = Mock(FhirResource) def invalidRule = Mock(ValidationRule) invalidRule.getMessage() >> ruleViolationMessage invalidRule.shouldRun(fhirBundle) >> true - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [invalidRule] + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [invalidRule] when: invalidRule.runRule(fhirBundle) >> { From 0fc78c570658f179b7c268c4c6a80c72482f604f Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:15:38 -0700 Subject: [PATCH 027/151] Changed naming --- .../etor/ruleengine/ValidationRule.java | 2 +- .../ruleengine/ValidationRuleEngineTest.groovy | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java index 74870b52f..f25db62f8 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java @@ -92,7 +92,7 @@ public void runRule(FhirResource resource) { boolean isValid = fhirEngine.evaluateCondition(resource.getUnderlyingResource(), validation); if (!isValid) { - logger.logWarning("Rule violation: " + message); + logger.logWarning("Validation failed: " + message); } } catch (Exception e) { logger.logError( diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index 5423d4e85..0a6e38a5f 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -89,22 +89,22 @@ class ValidationRuleEngineTest extends Specification { def "runRules handles logging warning correctly"() { given: - def ruleViolationMessage = "Rule violation message" - def fullRuleViolationMessage = "Validation failed: " + ruleViolationMessage + def failedValidationMessage = "Failed validation message" + def fullFailedValidationMessage = "Validation failed: " + failedValidationMessage def fhirBundle = Mock(FhirResource) def invalidRule = Mock(ValidationRule) - invalidRule.getMessage() >> ruleViolationMessage + invalidRule.getMessage() >> failedValidationMessage invalidRule.shouldRun(fhirBundle) >> true mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [invalidRule] when: invalidRule.runRule(fhirBundle) >> { - mockLogger.logWarning(fullRuleViolationMessage) + mockLogger.logWarning(fullFailedValidationMessage) } ruleEngine.runRules(fhirBundle) then: - 1 * mockLogger.logWarning(fullRuleViolationMessage) + 1 * mockLogger.logWarning(fullFailedValidationMessage) when: invalidRule.runRule(fhirBundle) >> null @@ -112,13 +112,13 @@ class ValidationRuleEngineTest extends Specification { ruleEngine.runRules(fhirBundle) then: - 0 * mockLogger.logWarning(fullRuleViolationMessage) + 0 * mockLogger.logWarning(fullFailedValidationMessage) when: invalidRule.shouldRun(fhirBundle) >> false ruleEngine.runRules(fhirBundle) then: - 0 * mockLogger.logWarning(fullRuleViolationMessage) + 0 * mockLogger.logWarning(fullFailedValidationMessage) } } From 95d4b2dee1aeaf5e94ad2001b0de6f559b5df39e Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 14:16:22 -0400 Subject: [PATCH 028/151] Update ValidationRuleEngineTest.groovy Update tests to work with refactoring changes --- .../ruleengine/ValidationRuleEngineTest.groovy | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index b3e84a546..321d2c59c 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -5,6 +5,8 @@ import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference import spock.lang.Specification +import java.nio.file.Path + class ValidationRuleEngineTest extends Specification { def ruleEngine = ValidationRuleEngine.getInstance("validation_definitions.json") def mockRuleLoader = Mock(RuleLoader) @@ -25,26 +27,26 @@ class ValidationRuleEngineTest extends Specification { def "ensureRulesLoaded happy path"() { given: - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] when: ruleEngine.ensureRulesLoaded() then: - 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] ruleEngine.rules.size() == 1 } def "ensureRulesLoaded loads rules only once by default"() { given: - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] when: ruleEngine.ensureRulesLoaded() ruleEngine.ensureRulesLoaded() // Call twice to test if rules are loaded only once then: - 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] ruleEngine.rules.size() == 1 } @@ -54,7 +56,7 @@ class ValidationRuleEngineTest extends Specification { def iterations = 4 when: - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] List threads = [] (1..threadsNum).each { threadId -> threads.add(new Thread({ @@ -67,13 +69,13 @@ class ValidationRuleEngineTest extends Specification { threads*.join() then: - 1 * mockRuleLoader.loadRules(_ as String, _ as TypeReference) + 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) } def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> { + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> { mockLogger.logError("Error loading rules", exception) return [] } @@ -93,7 +95,7 @@ class ValidationRuleEngineTest extends Specification { def invalidRule = Mock(ValidationRule) invalidRule.getMessage() >> ruleViolationMessage invalidRule.shouldRun(fhirBundle) >> true - mockRuleLoader.loadRules(_ as String, _ as TypeReference) >> [invalidRule] + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [invalidRule] when: invalidRule.runRule(fhirBundle) >> { From c0f0093a1ce6fe3e797bbd6974e7d07175c597ed Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:24:35 -0700 Subject: [PATCH 029/151] Added test coverage --- .../etor/ruleengine/ValidationRuleEngineTest.groovy | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index 0a6e38a5f..88bb90e57 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -121,4 +121,17 @@ class ValidationRuleEngineTest extends Specification { then: 0 * mockLogger.logWarning(fullFailedValidationMessage) } + + + def "runRules logs an error and doesn't run any rules when there's a RuleLoaderException"() { + given: + def exception = new RuleLoaderException("Error loading rules", new Exception()) + mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> { throw exception } + + when: + ruleEngine.runRules(Mock(FhirResource)) + + then: + 1 * mockLogger.logError(_ as String, exception) + } } From 2a5f7abb5c59ce24d54cf30bc61905d2bed1ccbc Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:31:20 -0700 Subject: [PATCH 030/151] Updated javadocs --- .../cdc/trustedintermediary/etor/ruleengine/RuleEngine.java | 5 ++++- .../etor/ruleengine/ValidationRuleEngine.java | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 7c4ffb11e..6357598d8 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -1,6 +1,9 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; -/** Manages the application of rules loaded from a definitions file using the RuleLoader. */ +/** + * The RuleEngine interface defines the structure for a rule engine. Each rule engine has methods to + * load rules, ensure rules are loaded, and run rules on a resource. + */ public interface RuleEngine { void unloadRules(); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java index 523aa674a..e409af3da 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java @@ -8,6 +8,7 @@ import java.util.List; import javax.inject.Inject; +/** Implements the RuleEngine interface. It represents a rule engine for validations. */ public class ValidationRuleEngine implements RuleEngine { private String ruleDefinitionsFileName; final List rules = new ArrayList<>(); From 79705c703aa6a94ae5a937035131625497ae6fda Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:24:02 -0700 Subject: [PATCH 031/151] Added CustomFhirTransformation interface --- .../etor/ruleengine/CustomFhirTransformation.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/CustomFhirTransformation.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/CustomFhirTransformation.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/CustomFhirTransformation.java new file mode 100644 index 000000000..44bff6d98 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/CustomFhirTransformation.java @@ -0,0 +1,7 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine; + +import java.util.Map; + +public interface CustomFhirTransformation { + FhirResource transform(FhirResource resource, Map args); +} From 4a5068db11f3a2c9cb1e9c25b3df468f6c8ca0a8 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:58:15 -0700 Subject: [PATCH 032/151] Created trasnformation package and moved transformation engine files --- .../trustedintermediary/etor/EtorDomainRegistration.java | 2 +- .../trustedintermediary/etor/orders/OrderController.java | 2 +- .../etor/ruleengine/{ => validation}/ValidationRule.java | 4 +++- .../ruleengine/{ => validation}/ValidationRuleEngine.java | 6 +++++- .../etor/orders/OrderControllerTest.groovy | 2 +- .../etor/ruleengine/RuleLoaderTest.groovy | 1 + .../ruleengine/ValidationRuleEngineIntegrationTest.groovy | 2 ++ .../etor/ruleengine/ValidationRuleEngineTest.groovy | 2 ++ .../etor/ruleengine/ValidationRuleTest.groovy | 1 + 9 files changed, 17 insertions(+), 5 deletions(-) rename etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => validation}/ValidationRule.java (94%) rename etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => validation}/ValidationRuleEngine.java (86%) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java index d839a91e3..bad7fbabc 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java @@ -34,7 +34,7 @@ import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender; import gov.hhs.cdc.trustedintermediary.etor.results.SendResultUseCase; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine; import gov.hhs.cdc.trustedintermediary.external.database.DatabaseMessageLinkStorage; import gov.hhs.cdc.trustedintermediary.external.database.DatabasePartnerMetadataStorage; import gov.hhs.cdc.trustedintermediary.external.database.DbDao; diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java index 4bd37e334..a841c29fd 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderController.java @@ -2,7 +2,7 @@ import gov.hhs.cdc.trustedintermediary.domainconnector.DomainRequest; import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrder; import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException; diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java similarity index 94% rename from etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java rename to etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java index f25db62f8..dfb0a42a1 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java @@ -1,6 +1,8 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation; import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import java.util.List; diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java similarity index 86% rename from etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java rename to etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java index e409af3da..fb1e1adc0 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java @@ -1,5 +1,9 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; import java.nio.file.Path; diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy index 034072f68..f1f8c794c 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy @@ -3,7 +3,7 @@ package gov.hhs.cdc.trustedintermediary.etor.orders import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.domainconnector.DomainRequest import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.ValidationRuleEngine +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy index 38f31ab70..85edab9fe 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy @@ -1,6 +1,7 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy index 3cef1191e..98903751c 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy @@ -1,6 +1,8 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index 88bb90e57..e52261f2d 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -1,6 +1,8 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference import spock.lang.Specification diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy index 623c6a3cd..b6fb44ad6 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy @@ -2,6 +2,7 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine import gov.hhs.cdc.trustedintermediary.FhirResourceMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.Logger From 643b31431693c13ab5753b9c1d3ce55aca216a60 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:00:39 -0700 Subject: [PATCH 033/151] Created trasnformation package and moved transformation engine files --- .../{ => transformation}/CustomFhirTransformation.java | 3 ++- .../{ => transformation}/TransformationRuleEngine.java | 5 ++++- .../etor/ruleengine/{ => validation}/TransformationRule.java | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) rename etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => transformation}/CustomFhirTransformation.java (52%) rename etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => transformation}/TransformationRuleEngine.java (52%) rename etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => validation}/TransformationRule.java (76%) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/CustomFhirTransformation.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java similarity index 52% rename from etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/CustomFhirTransformation.java rename to etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java index 44bff6d98..d4af38b18 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/CustomFhirTransformation.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java @@ -1,5 +1,6 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import java.util.Map; public interface CustomFhirTransformation { diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java similarity index 52% rename from etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngine.java rename to etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 41806478f..4d0d22e57 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -1,4 +1,7 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; public class TransformationRuleEngine implements RuleEngine { @Override diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/TransformationRule.java similarity index 76% rename from etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java rename to etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/TransformationRule.java index 499f8bddc..e3def1650 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/TransformationRule.java @@ -1,5 +1,7 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; import java.util.List; public class TransformationRule implements Rule { From 9fd650663785ba9461963bc34e304817b651424b Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:13:32 -0700 Subject: [PATCH 034/151] Added example for custom transform method for addEtorProcessingTag --- .../custom/addEtorProcessingTag.java | 32 +++++++++++++++++++ .../hapi/HapiMessageConverterHelper.java | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java new file mode 100644 index 000000000..d80b35531 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java @@ -0,0 +1,32 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; + +import static gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper.findOrInitializeMessageHeader; + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; +import java.util.Map; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Meta; + +public class addEtorProcessingTag implements CustomFhirTransformation { + + @Override + public FhirResource transform(FhirResource resource, Map args) { + Bundle bundle = (Bundle) resource.getUnderlyingResource(); + var messageHeader = findOrInitializeMessageHeader(bundle); + var meta = messageHeader.hasMeta() ? messageHeader.getMeta() : new Meta(); + + var systemValue = "http://localcodes.org/ETOR"; + var codeValue = "ETOR"; + var displayValue = "Processed by ETOR"; + + if (meta.getTag(systemValue, codeValue) == null) { + meta.addTag(new Coding(systemValue, codeValue, displayValue)); + } + + messageHeader.setMeta(meta); + + return resource; + } +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java index 46b95bf1c..ee867b52e 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java @@ -50,7 +50,7 @@ public void addEtorTagToBundle(Bundle messageBundle) { * @param bundle the in coming message in a FHIR bundle * @return returns existing MessageHeader resource or a newly created one */ - public MessageHeader findOrInitializeMessageHeader(Bundle bundle) { + public static MessageHeader findOrInitializeMessageHeader(Bundle bundle) { var messageHeader = HapiHelper.resourcesInBundle(bundle, MessageHeader.class).findFirst().orElse(null); if (messageHeader == null) { From a3e9ddc7a78f093d32a6767b640a5095ebbed114 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Mon, 29 Apr 2024 16:35:52 -0500 Subject: [PATCH 035/151] Move convertToOrder into the Custom FhirTransformation --- .../transformation/custom/convertToOrder.java | 120 ++++++++++++++++++ .../hapi/HapiMessageConverterHelper.java | 11 ++ 2 files changed, 131 insertions(+) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java new file mode 100644 index 000000000..7f082cb43 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java @@ -0,0 +1,120 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; +import java.time.Instant; +import java.util.Date; +import java.util.Map; +import java.util.UUID; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.MessageHeader; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Provenance; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.ServiceRequest; +import org.hl7.fhir.r4.model.UrlType; + +public class convertToOrder implements CustomFhirTransformation { + + private static final Coding OML_CODING = + new Coding( + "http://terminology.hl7.org/CodeSystem/v2-0003", + "O21", + "OML - Laboratory order"); + + @Override + public FhirResource transform( + final FhirResource resource, final Map args) { + Bundle bundle = (Bundle) resource.getUnderlyingResource(); + + var overallId = UUID.randomUUID().toString(); + if (!bundle.hasId()) { + bundle.setId(overallId); + } + + if (!bundle.hasIdentifier()) { + bundle.setIdentifier(new Identifier().setValue(overallId)); + } + + var orderDateTime = Date.from(Instant.now()); + if (!bundle.hasTimestamp()) { + bundle.setTimestamp(orderDateTime); + } + + bundle.setType( + Bundle.BundleType.MESSAGE); // it always needs to be a message, so no if statement + + var patient = HapiMessageConverterHelper.findPatientOrNull(bundle); + + var serviceRequest = createServiceRequest(patient, orderDateTime); + var messageHeader = createMessageHeader(); + var provenance = createProvenanceResource(orderDateTime); + + bundle.getEntry().add(0, new Bundle.BundleEntryComponent().setResource(messageHeader)); + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(serviceRequest)); + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(provenance)); + + return resource; + } + + private MessageHeader createMessageHeader() { + var messageHeader = new MessageHeader(); + + messageHeader.setId(UUID.randomUUID().toString()); + + messageHeader.setEvent(OML_CODING); + + var meta = new Meta(); + + // Adding processing id of 'P' + meta.addTag("http://terminology.hl7.org/CodeSystem/v2-0103", "P", "Production"); + + messageHeader.setMeta(meta); + + messageHeader.setSource( + new MessageHeader.MessageSourceComponent( + new UrlType("https://reportstream.cdc.gov/")) + .setName("CDC Trusted Intermediary")); + + return messageHeader; + } + + private ServiceRequest createServiceRequest(final Patient patient, final Date orderDateTime) { + var serviceRequest = new ServiceRequest(); + + serviceRequest.setId(UUID.randomUUID().toString()); + + serviceRequest.setStatus(ServiceRequest.ServiceRequestStatus.ACTIVE); + + serviceRequest.setIntent(ServiceRequest.ServiceRequestIntent.ORDER); + + serviceRequest.setCode( + new CodeableConcept( + new Coding("http://loinc.org", "54089-8", "Newborn Screening Panel"))); + + serviceRequest.addCategory( + new CodeableConcept( + new Coding("http://snomed.info/sct", "108252007", "Laboratory procedure"))); + + serviceRequest.setSubject(new Reference(patient)); + + serviceRequest.setAuthoredOn(orderDateTime); + + return serviceRequest; + } + + private Provenance createProvenanceResource(Date orderDate) { + var provenance = new Provenance(); + + provenance.setId(UUID.randomUUID().toString()); + provenance.setRecorded(orderDate); + provenance.setActivity(new CodeableConcept(OML_CODING)); + + return provenance; + } +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java index ee867b52e..54e791d9c 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java @@ -6,6 +6,7 @@ import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Patient; /** * Helper class with a variety of utilities to use on a FHIR bundle message. It adds the 'ETOR' tag @@ -59,4 +60,14 @@ public static MessageHeader findOrInitializeMessageHeader(Bundle bundle) { } return messageHeader; } + + /** + * Finds the first patient in bundle resource or returns null + * + * @param bundle + * @return Patient if found + */ + public static Patient findPatientOrNull(Bundle bundle) { + return HapiHelper.resourcesInBundle(bundle, Patient.class).findFirst().orElse(null); + } } From 2bd21c8e5ce94cdc994fa525e70650d27fd70b34 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:30:21 -0700 Subject: [PATCH 036/151] Changed implementation for rules implementation to inherit from Rule class --- .../etor/ruleengine/Rule.java | 83 +++++++++++++++--- .../ruleengine/validation/ValidationRule.java | 84 +++---------------- ...ValidationRuleEngineIntegrationTest.groovy | 14 ++-- 3 files changed, 91 insertions(+), 90 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java index e7d27365f..1b421cfed 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java @@ -1,24 +1,85 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine; +import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; +import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir; +import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import java.util.List; /** - * The Rule interface defines the structure for a rule in the rule engine. Each rule has a name, - * description, warning message, conditions, validations, and methods to check if a resource is - * valid and if the rule applies to a resource. + * Represents a rule that can be run on a FHIR resource. Each rule has a name, description, logging + * message, conditions to determine if the rule should run, and actions to run in case the condition + * is met. */ -public interface Rule { - String getName(); +public class Rule { - String getDescription(); + protected final Logger logger = ApplicationContext.getImplementation(Logger.class); + protected final HapiFhir fhirEngine = ApplicationContext.getImplementation(HapiFhir.class); + private String name; + private String description; + private String message; + private List conditions; + private List rules; - String getMessage(); + /** + * Do not delete this constructor! It is used for JSON deserialization when loading rules from a + * file. + */ + public Rule() {} - List getConditions(); + public Rule( + String ruleName, + String ruleDescription, + String ruleMessage, + List ruleConditions, + List ruleActions) { + name = ruleName; + description = ruleDescription; + message = ruleMessage; + conditions = ruleConditions; + rules = ruleActions; + } - List getRules(); + public String getName() { + return name; + } - boolean shouldRun(FhirResource resource); + public String getDescription() { + return description; + } - void runRule(FhirResource resource); + public String getMessage() { + return message; + } + + public List getConditions() { + return conditions; + } + + public List getRules() { + return rules; + } + + public boolean shouldRun(FhirResource resource) { + return conditions.stream() + .allMatch( + condition -> { + try { + return fhirEngine.evaluateCondition( + resource.getUnderlyingResource(), condition); + } catch (Exception e) { + logger.logError( + "Rule [" + + name + + "]: " + + "An error occurred while evaluating the condition: " + + condition, + e); + return false; + } + }); + } + + public void runRule(FhirResource resource) { + throw new UnsupportedOperationException("This method must be implemented by subclasses."); + } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java index dfb0a42a1..abaacfa42 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java @@ -1,26 +1,16 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation; -import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; -import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir; -import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import java.util.List; /** - * Implements the Rule interface. It represents a rule with a name, description, warning message, - * conditions, and validations. It uses the HapiFhir engine to evaluate the conditions and - * validations. + * The ValidationRule class extends the {@link gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule + * Rule} class and represents a validation rule. It implements the {@link + * gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule#runRule(FhirResource) runRule} method to + * evaluate the validation and log a warning if the validation fails. */ -public class ValidationRule implements Rule { - - private final Logger logger = ApplicationContext.getImplementation(Logger.class); - private final HapiFhir fhirEngine = ApplicationContext.getImplementation(HapiFhir.class); - private String name; - private String description; - private String message; - private List conditions; - private List rules; +public class ValidationRule extends Rule { /** * Do not delete this constructor! It is used for JSON deserialization when loading rules from a @@ -34,72 +24,22 @@ public ValidationRule( String ruleMessage, List ruleConditions, List ruleActions) { - name = ruleName; - description = ruleDescription; - message = ruleMessage; - conditions = ruleConditions; - rules = ruleActions; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getDescription() { - return description; - } - - @Override - public String getMessage() { - return message; - } - - @Override - public List getConditions() { - return conditions; - } - - @Override - public List getRules() { - return rules; - } - - @Override - public boolean shouldRun(FhirResource resource) { - return conditions.stream() - .allMatch( - condition -> { - try { - return fhirEngine.evaluateCondition( - resource.getUnderlyingResource(), condition); - } catch (Exception e) { - logger.logError( - "Rule [" - + name - + "]: " - + "An error occurred while evaluating the condition: " - + condition, - e); - return false; - } - }); + super(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions); } - @Override public void runRule(FhirResource resource) { - for (String validation : rules) { + for (String validation : this.getRules()) { try { boolean isValid = - fhirEngine.evaluateCondition(resource.getUnderlyingResource(), validation); + this.fhirEngine.evaluateCondition( + resource.getUnderlyingResource(), validation); if (!isValid) { - logger.logWarning("Validation failed: " + message); + this.logger.logWarning("Validation failed: " + this.getMessage()); } } catch (Exception e) { - logger.logError( + this.logger.logError( "Rule [" - + name + + this.getName() + "]: " + "An error occurred while evaluating the validation: " + validation, diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy index 98903751c..001ce7bff 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy @@ -66,7 +66,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { given: def fhirResource = getExampleFhirResource("e2e/orders/001_OML_O21_short.fhir") def validation = "Bundle.entry.resource.ofType(MessageHeader).focus.resolve().category.exists()" - Rule rule = createValidationRule([], [validation]) + def rule = createValidationRule([], [validation]) when: rule.runRule(fhirResource) @@ -184,13 +184,13 @@ class ValidationRuleEngineIntegrationTest extends Specification { return bundle } - Rule createValidationRule(List ruleConditions, List ruleValidations) { + ValidationRule createValidationRule(List ruleConditions, List ruleValidations) { return new ValidationRule( - name: "Rule name", - description: "Rule description", - message: "Rule warning message", - conditions: ruleConditions, - rules: ruleValidations, + "Rule name", + "Rule description", + "Rule warning message", + ruleConditions, + ruleValidations, ) } From 6ba19436264d0d2515cd6f69e5589959705f2d84 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:30:39 -0700 Subject: [PATCH 037/151] Clean up --- .../etor/ruleengine/validation/ValidationRuleEngine.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java index fb1e1adc0..8554a6394 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java @@ -48,7 +48,7 @@ public void ensureRulesLoaded() throws RuleLoaderException { List parsedRules = ruleLoader.loadRules(path, new TypeReference<>() {}); - loadRules(parsedRules); + this.rules.addAll(parsedRules); } } } @@ -68,8 +68,4 @@ public void runRules(FhirResource resource) { } } } - - private synchronized void loadRules(List rules) { - this.rules.addAll(rules); - } } From 2ee05052a7a2457ec5455ca41439a6448a14c484 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:42:48 -0700 Subject: [PATCH 038/151] Update transformation rule implementation --- .../transformation/TransformationRule.java | 23 +++++++++++ .../validation/TransformationRule.java | 40 ------------------- 2 files changed, 23 insertions(+), 40 deletions(-) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/TransformationRule.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java new file mode 100644 index 000000000..2054ffeb5 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -0,0 +1,23 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; +import java.util.List; + +public class TransformationRule extends Rule { + + public TransformationRule( + String ruleName, + String ruleDescription, + String ruleMessage, + List ruleConditions, + List ruleActions) { + super(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions); + } + + public void runRule(FhirResource resource) { + for (String transformation : this.getRules()) { + // todo: implement transformation + } + } +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/TransformationRule.java deleted file mode 100644 index e3def1650..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/TransformationRule.java +++ /dev/null @@ -1,40 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation; - -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; -import java.util.List; - -public class TransformationRule implements Rule { - @Override - public String getName() { - return ""; - } - - @Override - public String getDescription() { - return ""; - } - - @Override - public String getMessage() { - return ""; - } - - @Override - public List getConditions() { - return List.of(); - } - - @Override - public List getRules() { - return List.of(); - } - - @Override - public boolean shouldRun(FhirResource resource) { - return false; - } - - @Override - public void runRule(FhirResource resource) {} -} From 70851c29272845d4c8ab0ad42bff2371f4d8b4b8 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:51:05 -0700 Subject: [PATCH 039/151] Added implementation for TransformationRuleEngine --- .../etor/EtorDomainRegistration.java | 4 ++ .../transformation/TransformationRule.java | 7 +++ .../TransformationRuleEngine.java | 62 ++++++++++++++++++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java index bad7fbabc..760e16b0f 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java @@ -34,6 +34,7 @@ import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender; import gov.hhs.cdc.trustedintermediary.etor.results.SendResultUseCase; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine; import gov.hhs.cdc.trustedintermediary.external.database.DatabaseMessageLinkStorage; import gov.hhs.cdc.trustedintermediary.external.database.DatabasePartnerMetadataStorage; @@ -136,6 +137,9 @@ public Map> domainRegistra ApplicationContext.register( ValidationRuleEngine.class, ValidationRuleEngine.getInstance("validation_definitions.json")); + ApplicationContext.register( + TransformationRuleEngine.class, + TransformationRuleEngine.getInstance("transformation_definitions.json")); ApplicationContext.register(SendMessageHelper.class, SendMessageHelper.getInstance()); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 2054ffeb5..40676be56 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -4,6 +4,13 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; import java.util.List; +/** + * The TransformationRule class extends the {@link + * gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule Rule} class and represents a transformation + * rule. It implements the {@link + * gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule#runRule(FhirResource) runRule} method to + * apply a transformation to the FHIR resource. + */ public class TransformationRule extends Rule { public TransformationRule( diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 4d0d22e57..bbaec712c 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -2,14 +2,70 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; +import gov.hhs.cdc.trustedintermediary.wrappers.Logger; +import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +/** Implements the RuleEngine interface. It represents a rule engine for transformations. */ public class TransformationRuleEngine implements RuleEngine { + private String ruleDefinitionsFileName; + final List rules = new ArrayList<>(); + + private static final TransformationRuleEngine INSTANCE = new TransformationRuleEngine(); + + @Inject Logger logger; + @Inject RuleLoader ruleLoader; + + public static TransformationRuleEngine getInstance(String ruleDefinitionsFileName) { + INSTANCE.ruleDefinitionsFileName = ruleDefinitionsFileName; + return INSTANCE; + } + + private TransformationRuleEngine() {} + @Override - public void unloadRules() {} + public void unloadRules() { + rules.clear(); + } @Override - public void ensureRulesLoaded() {} + public void ensureRulesLoaded() throws RuleLoaderException { + if (rules.isEmpty()) { + synchronized (this) { + if (rules.isEmpty()) { + Path path = + Paths.get( + getClass() + .getClassLoader() + .getResource(ruleDefinitionsFileName) + .getPath()); + + List parsedRules = + ruleLoader.loadRules(path, new TypeReference<>() {}); + this.rules.addAll(parsedRules); + } + } + } + } @Override - public void runRules(FhirResource resource) {} + public void runRules(FhirResource resource) { + try { + ensureRulesLoaded(); + } catch (RuleLoaderException e) { + logger.logError("Failed to load rules definitions", e); + return; + } + for (TransformationRule rule : rules) { + if (rule.shouldRun(resource)) { + rule.runRule(resource); + } + } + } } From 8597f88504c2d21d05d0d4b0a76bdd2e707d3f68 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:05:37 -0700 Subject: [PATCH 040/151] Added flexibility to handle different rule types and added TransformationRuleMethod type --- .../cdc/trustedintermediary/etor/ruleengine/Rule.java | 8 ++++---- .../ruleengine/transformation/TransformationRule.java | 11 +++++++---- .../transformation/TransformationRuleMethod.java | 5 +++++ .../etor/ruleengine/validation/ValidationRule.java | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleMethod.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java index 1b421cfed..58453bb64 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java @@ -10,7 +10,7 @@ * message, conditions to determine if the rule should run, and actions to run in case the condition * is met. */ -public class Rule { +public class Rule { protected final Logger logger = ApplicationContext.getImplementation(Logger.class); protected final HapiFhir fhirEngine = ApplicationContext.getImplementation(HapiFhir.class); @@ -18,7 +18,7 @@ public class Rule { private String description; private String message; private List conditions; - private List rules; + private List rules; /** * Do not delete this constructor! It is used for JSON deserialization when loading rules from a @@ -31,7 +31,7 @@ public Rule( String ruleDescription, String ruleMessage, List ruleConditions, - List ruleActions) { + List ruleActions) { name = ruleName; description = ruleDescription; message = ruleMessage; @@ -55,7 +55,7 @@ public List getConditions() { return conditions; } - public List getRules() { + public List getRules() { return rules; } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 40676be56..3ef956581 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -3,6 +3,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; import java.util.List; +import java.util.Map; /** * The TransformationRule class extends the {@link @@ -11,20 +12,22 @@ * gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule#runRule(FhirResource) runRule} method to * apply a transformation to the FHIR resource. */ -public class TransformationRule extends Rule { +public class TransformationRule extends Rule { public TransformationRule( String ruleName, String ruleDescription, String ruleMessage, List ruleConditions, - List ruleActions) { + List ruleActions) { super(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions); } public void runRule(FhirResource resource) { - for (String transformation : this.getRules()) { - // todo: implement transformation + for (TransformationRuleMethod transformation : this.getRules()) { + String methodName = transformation.name(); + Map methodArgs = transformation.args(); + // evaluate methodName(methodArgs) and apply transformation to resource } } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleMethod.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleMethod.java new file mode 100644 index 000000000..d93211c6b --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleMethod.java @@ -0,0 +1,5 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; + +import java.util.Map; + +public record TransformationRuleMethod(String name, Map args) {} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java index abaacfa42..9e39bdfc2 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java @@ -10,7 +10,7 @@ * gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule#runRule(FhirResource) runRule} method to * evaluate the validation and log a warning if the validation fails. */ -public class ValidationRule extends Rule { +public class ValidationRule extends Rule { /** * Do not delete this constructor! It is used for JSON deserialization when loading rules from a From d78d0bac0229f7f0f87dc765835b84d9543db777 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:14:03 -0700 Subject: [PATCH 041/151] Updated todo comment with pending work --- .../etor/ruleengine/transformation/TransformationRule.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 3ef956581..a2a70356d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -27,7 +27,11 @@ public void runRule(FhirResource resource) { for (TransformationRuleMethod transformation : this.getRules()) { String methodName = transformation.name(); Map methodArgs = transformation.args(); - // evaluate methodName(methodArgs) and apply transformation to resource + // evaluate methodName(resource, methodArgs) + // i.e. addEtorProcessingTag(resource, methodArgs) where methodArgs is empty + // first, need to get methodName from ruleengine/transformation/custom/ using the class + // name or file name + // then, need to call the method with the resource and methodArgs } } } From a03dd0b0eeed3c4371e828e7fe872fe0b1dba851 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:28:28 -0700 Subject: [PATCH 042/151] Added missing override tag --- .../etor/ruleengine/validation/ValidationRule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java index abaacfa42..4dbcdc57f 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRule.java @@ -27,6 +27,7 @@ public ValidationRule( super(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions); } + @Override public void runRule(FhirResource resource) { for (String validation : this.getRules()) { try { From e9290aa4f47e0b2df2d0e162b2538c62f12d68f5 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 20:07:30 -0400 Subject: [PATCH 043/151] Create RuleTest.groovy Added test-case for base Rule class exclusive implementation. --- .../etor/ruleengine/RuleTest.groovy | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleTest.groovy diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleTest.groovy new file mode 100644 index 000000000..3f335d233 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleTest.groovy @@ -0,0 +1,29 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine + +import gov.hhs.cdc.trustedintermediary.FhirResourceMock +import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir +import gov.hhs.cdc.trustedintermediary.wrappers.Logger +import spock.lang.Specification + +class RuleTest extends Specification { + + def setup() { + TestApplicationContext.reset() + TestApplicationContext.init() + TestApplicationContext.register(Logger, Mock(Logger)) + TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) + TestApplicationContext.injectRegisteredImplementations() + } + + def "runRule throws an UnsupportedOperationException when ran from the Rule class"() { + given: + def rule = new Rule() + + when: + rule.runRule(new FhirResourceMock("resource")) + + then: + thrown(UnsupportedOperationException) + } +} From 7021d857cccfcc2a7b5294c25ad416f8a8d82f3f Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 21:33:13 -0400 Subject: [PATCH 044/151] Update HapiMessageConverterHelper.java Add helper for multiple patients --- .../external/hapi/HapiMessageConverterHelper.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java index 54e791d9c..1515976d7 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java @@ -1,6 +1,7 @@ package gov.hhs.cdc.trustedintermediary.external.hapi; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; +import java.util.stream.Stream; import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Coding; @@ -70,4 +71,14 @@ public static MessageHeader findOrInitializeMessageHeader(Bundle bundle) { public static Patient findPatientOrNull(Bundle bundle) { return HapiHelper.resourcesInBundle(bundle, Patient.class).findFirst().orElse(null); } + + /** + * Finds all patient resources inside a given bundle + * + * @param bundle Bundle to check + * @return Stream list of patients. + */ + public static Stream findAllPatients(Bundle bundle) { + return HapiHelper.resourcesInBundle(bundle, Patient.class); + } } From 53445b30c7a58c9674918aa895ad292cc03b1247 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 21:33:31 -0400 Subject: [PATCH 045/151] Create addContactSectionToPatientResource.java --- .../addContactSectionToPatientResource.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java new file mode 100644 index 000000000..2eb6d10f2 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java @@ -0,0 +1,41 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; +import java.util.List; +import java.util.Map; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Coding; + +public class addContactSectionToPatientResource implements CustomFhirTransformation { + private final List CODING_LIST = + List.of( + new Coding( + "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "MTH", "mother")); + + @Override + public FhirResource transform(FhirResource resource, Map args) { + Bundle bundle = (Bundle) resource.getUnderlyingResource(); + + var patients = HapiMessageConverterHelper.findAllPatients(bundle); + + patients.forEach( + p -> { + var myContact = p.addContact(); + var motherRelationship = myContact.addRelationship(); + motherRelationship.setCoding(CODING_LIST); + + var mothersMaidenNameExtension = + p.getExtensionByUrl( + "http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName"); + if (mothersMaidenNameExtension != null) { + myContact.setName(p.castToHumanName(mothersMaidenNameExtension.getValue())); + } + myContact.setTelecom(p.getTelecom()); + myContact.setAddress(p.getAddressFirstRep()); + }); + + return resource; + } +} From db0cddf071e09de20dc1577b891445d084434111 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 29 Apr 2024 21:50:45 -0400 Subject: [PATCH 046/151] Create convertToOmlOrder.java --- .../custom/convertToOmlOrder.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java new file mode 100644 index 000000000..3fbd0d5ae --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java @@ -0,0 +1,27 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; + +import static gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper.findOrInitializeMessageHeader; + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; +import java.util.Map; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Coding; + +public class convertToOmlOrder implements CustomFhirTransformation { + private final Coding OML_CODING = + new Coding( + "http://terminology.hl7.org/CodeSystem/v2-0003", + "O21", + "OML - Laboratory order"); + + @Override + public FhirResource transform(FhirResource resource, Map args) { + Bundle bundle = (Bundle) resource.getUnderlyingResource(); + + var messageHeader = findOrInitializeMessageHeader(bundle); + messageHeader.setEvent(OML_CODING); + + return resource; + } +} From b3aeec9bde37eba6352f998c916413283db90f0b Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 08:12:18 -0700 Subject: [PATCH 047/151] Implemented custom method evaluation. Still need to test --- .../transformation/TransformationRule.java | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index a2a70356d..84619b932 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -2,6 +2,10 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -24,14 +28,44 @@ public TransformationRule( } public void runRule(FhirResource resource) { + Path customTransformatiosPath = + Path.of( + "etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/"); + File[] customTransformationFiles = customTransformatiosPath.toFile().listFiles(); + + if (customTransformationFiles == null) { + logger.logInfo("No custom transformation files found."); + return; + } + for (TransformationRuleMethod transformation : this.getRules()) { String methodName = transformation.name(); Map methodArgs = transformation.args(); - // evaluate methodName(resource, methodArgs) - // i.e. addEtorProcessingTag(resource, methodArgs) where methodArgs is empty - // first, need to get methodName from ruleengine/transformation/custom/ using the class - // name or file name - // then, need to call the method with the resource and methodArgs + + for (File file : customTransformationFiles) { + if (file.isFile() && (file.getName().endsWith(".class"))) { + String className = file.getName().replace(".class", ""); + if (className.equalsIgnoreCase(methodName)) { + try { + Class clazz = Class.forName(className); + Method method = + clazz.getDeclaredMethod( + "transform", FhirResource.class, Map.class); + method.invoke( + clazz.getDeclaredConstructor().newInstance(), + resource, + methodArgs); + return; + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InvocationTargetException + | InstantiationException e) { + logger.logError("Error invoking method: " + e.getMessage()); + } + } + } + } } } } From 4392b2714b8026c634213a3fc21f0b09e8def748 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 08:14:19 -0700 Subject: [PATCH 048/151] Added failing integration test for TransformationRuleEngine --- ...sformationRuleEngineIntegrationTest.groovy | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy new file mode 100644 index 000000000..03c52d09f --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy @@ -0,0 +1,43 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine + +import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource +import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson +import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir +import gov.hhs.cdc.trustedintermediary.wrappers.Logger +import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter +import org.hl7.fhir.r4.model.Bundle +import spock.lang.Specification + +class TransformationRuleEngineIntegrationTest extends Specification { + def fhir = HapiFhirImplementation.getInstance() + def engine = TransformationRuleEngine.getInstance("transformation_definitions.json") + def mockLogger = Mock(Logger) + + def setup() { + TestApplicationContext.reset() + TestApplicationContext.init() + + TestApplicationContext.register(Formatter, Jackson.getInstance()) + TestApplicationContext.register(HapiFhir, fhir) + TestApplicationContext.register(ValidationRuleEngine, engine) + TestApplicationContext.register(RuleLoader, RuleLoader.getInstance()) + TestApplicationContext.register(Logger, mockLogger) + + TestApplicationContext.injectRegisteredImplementations() + } + + def "transformation rules run"() { + given: + def bundle = new Bundle() + + when: + engine.runRules(new HapiFhirResource(bundle)) + + then: + 0 * mockLogger.logWarning(_ as String) + } +} From 0ab622e68342f7a356fad89efa3e3784c375c8a9 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:02:49 -0700 Subject: [PATCH 049/151] Fixed TransformationRule mapping --- .../etor/ruleengine/transformation/TransformationRule.java | 6 ++++++ .../TransformationRuleEngineIntegrationTest.groovy | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 84619b932..0f927f27a 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -18,6 +18,12 @@ */ public class TransformationRule extends Rule { + /** + * Do not delete this constructor! It is used for JSON deserialization when loading rules from a + * file. + */ + public TransformationRule() {} + public TransformationRule( String ruleName, String ruleDescription, diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy index 03c52d09f..ae2245272 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy @@ -23,7 +23,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.register(Formatter, Jackson.getInstance()) TestApplicationContext.register(HapiFhir, fhir) - TestApplicationContext.register(ValidationRuleEngine, engine) + TestApplicationContext.register(TransformationRuleEngine, engine) TestApplicationContext.register(RuleLoader, RuleLoader.getInstance()) TestApplicationContext.register(Logger, mockLogger) From 63518ba135c63a4dfbd0561ac8ff84f948931744 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:40:30 -0700 Subject: [PATCH 050/151] Fixed custom method evaluation method --- .../transformation/TransformationRule.java | 67 ++++++++++--------- .../resources/transformation_definitions.json | 2 +- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 0f927f27a..c5225719b 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -6,6 +6,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Map; @@ -34,44 +35,44 @@ public TransformationRule( } public void runRule(FhirResource resource) { - Path customTransformatiosPath = - Path.of( - "etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/"); - File[] customTransformationFiles = customTransformatiosPath.toFile().listFiles(); - if (customTransformationFiles == null) { - logger.logInfo("No custom transformation files found."); - return; + for (TransformationRuleMethod transformation : this.getRules()) { + String name = transformation.name(); + Map args = transformation.args(); + + try { + Class clazz = loadCustomTransformationClassFromFile(name); + Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); + method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InvocationTargetException + | InstantiationException e) { + logger.logError("Error invoking method: " + e.getMessage()); + } } + } - for (TransformationRuleMethod transformation : this.getRules()) { - String methodName = transformation.name(); - Map methodArgs = transformation.args(); + private static Class loadCustomTransformationClassFromFile(String className) + throws ClassNotFoundException { + String customPackageName = + "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; + Path customTransformationPath = + Paths.get( + "src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/"); + File[] customTransformationFiles = customTransformationPath.toFile().listFiles(); + assert customTransformationFiles != null; - for (File file : customTransformationFiles) { - if (file.isFile() && (file.getName().endsWith(".class"))) { - String className = file.getName().replace(".class", ""); - if (className.equalsIgnoreCase(methodName)) { - try { - Class clazz = Class.forName(className); - Method method = - clazz.getDeclaredMethod( - "transform", FhirResource.class, Map.class); - method.invoke( - clazz.getDeclaredConstructor().newInstance(), - resource, - methodArgs); - return; - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InvocationTargetException - | InstantiationException e) { - logger.logError("Error invoking method: " + e.getMessage()); - } - } - } + for (File file : customTransformationFiles) { + String fileName = file.getName().replace(".java", ""); + if (file.isFile() + && (file.getName().endsWith(".java")) + && (fileName.equalsIgnoreCase(className))) { + return Class.forName(customPackageName + "." + className); } } + + throw new ClassNotFoundException("No custom transformation file found for " + className); } } diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 39cc9c043..3c8d7765d 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -6,7 +6,7 @@ "conditions": [ ], "rules": [ { - "name": "addEtorTagToBundle", + "name": "addEtorProcessingTag", "args": { } } ] From 9e49e2403df9c1636e599ac558397e81cf1c3372 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:50:31 -0700 Subject: [PATCH 051/151] Added better assertion for integration test --- .../ruleengine/TransformationRuleEngineIntegrationTest.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy index ae2245272..487a1ad0f 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy @@ -30,7 +30,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.injectRegisteredImplementations() } - def "transformation rules run"() { + def "transformation rules run without error"() { given: def bundle = new Bundle() @@ -38,6 +38,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { engine.runRules(new HapiFhirResource(bundle)) then: - 0 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Exception) } } From 79bd441542a7af70eaaac32bca44cc853a4cc3b2 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:56:44 -0700 Subject: [PATCH 052/151] Updated transformation definitions --- .../resources/transformation_definitions.json | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 3c8d7765d..550004854 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -11,9 +11,23 @@ } ] }, + { + "name": "convertToOrder", + "description": "Convert Demographics to Order", + "message": "", + "conditions": [ + "Condition to filter demographics messages" + ], + "rules": [ + { + "name": "convertToOrder", + "args": { } + } + ] + }, { "name": "convertToOmlOrder", - "description": "Convert to OML Order", + "description": "Convert ORM to OML Order", "message": "", "conditions": [ "Bundle.entry.resource.ofType(MessageHeader).event.code = 'O01'" @@ -27,10 +41,10 @@ }, { "name": "addContactSectionToPatientResource", - "description": "Add Contact Section to Patient Resource", + "description": "Add Contact Section to Patient Resource in OML Order", "message": "", "conditions": [ - "Bundle.entry.resource.ofType(MessageHeader).event.code = 'O01'" + "Bundle.entry.resource.ofType(MessageHeader).event.code = 'O21'" ], "rules": [ { From a46ac926108e92879722bc312577fbe6a5ac4aab Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:07:36 -0700 Subject: [PATCH 053/151] Fixed error message --- .../etor/ruleengine/transformation/TransformationRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index c5225719b..b2deac3e4 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -49,7 +49,7 @@ public void runRule(FhirResource resource) { | IllegalAccessException | InvocationTargetException | InstantiationException e) { - logger.logError("Error invoking method: " + e.getMessage()); + logger.logError("Error invoking method: " + name, e); } } } From 15c59c248a7fcea676baffe52926566d983d9382 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:12:41 -0700 Subject: [PATCH 054/151] Updated CustomFhirTransformation interface to return void as we don't need to return the transformed FhirResource --- .../CustomFhirTransformation.java | 2 +- .../addContactSectionToPatientResource.java | 4 +- .../custom/addEtorProcessingTag.java | 9 +- .../custom/convertToOmlOrder.java | 4 +- .../transformation/custom/convertToOrder.java | 83 ++----------------- .../hapi/HapiMessageConverterHelper.java | 76 +++++++++++++++++ 6 files changed, 87 insertions(+), 91 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java index d4af38b18..8aa178d62 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java @@ -4,5 +4,5 @@ import java.util.Map; public interface CustomFhirTransformation { - FhirResource transform(FhirResource resource, Map args); + void transform(FhirResource resource, Map args); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java index 2eb6d10f2..418d48fb0 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java @@ -15,7 +15,7 @@ public class addContactSectionToPatientResource implements CustomFhirTransformat "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "MTH", "mother")); @Override - public FhirResource transform(FhirResource resource, Map args) { + public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); var patients = HapiMessageConverterHelper.findAllPatients(bundle); @@ -35,7 +35,5 @@ public FhirResource transform(FhirResource resource, Map a myContact.setTelecom(p.getTelecom()); myContact.setAddress(p.getAddressFirstRep()); }); - - return resource; } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java index d80b35531..c8acf7c9d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java @@ -1,9 +1,8 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; -import static gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper.findOrInitializeMessageHeader; - import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Coding; @@ -12,9 +11,9 @@ public class addEtorProcessingTag implements CustomFhirTransformation { @Override - public FhirResource transform(FhirResource resource, Map args) { + public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); - var messageHeader = findOrInitializeMessageHeader(bundle); + var messageHeader = HapiMessageConverterHelper.findOrInitializeMessageHeader(bundle); var meta = messageHeader.hasMeta() ? messageHeader.getMeta() : new Meta(); var systemValue = "http://localcodes.org/ETOR"; @@ -26,7 +25,5 @@ public FhirResource transform(FhirResource resource, Map a } messageHeader.setMeta(meta); - - return resource; } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java index 3fbd0d5ae..5f8cd259c 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java @@ -16,12 +16,10 @@ public class convertToOmlOrder implements CustomFhirTransformation { "OML - Laboratory order"); @Override - public FhirResource transform(FhirResource resource, Map args) { + public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); var messageHeader = findOrInitializeMessageHeader(bundle); messageHeader.setEvent(OML_CODING); - - return resource; } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java index 7f082cb43..dd946f9aa 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java @@ -8,28 +8,12 @@ import java.util.Map; import java.util.UUID; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.MessageHeader; -import org.hl7.fhir.r4.model.Meta; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Provenance; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.ServiceRequest; -import org.hl7.fhir.r4.model.UrlType; public class convertToOrder implements CustomFhirTransformation { - private static final Coding OML_CODING = - new Coding( - "http://terminology.hl7.org/CodeSystem/v2-0003", - "O21", - "OML - Laboratory order"); - @Override - public FhirResource transform( - final FhirResource resource, final Map args) { + public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); var overallId = UUID.randomUUID().toString(); @@ -51,70 +35,13 @@ public FhirResource transform( var patient = HapiMessageConverterHelper.findPatientOrNull(bundle); - var serviceRequest = createServiceRequest(patient, orderDateTime); - var messageHeader = createMessageHeader(); - var provenance = createProvenanceResource(orderDateTime); + var serviceRequest = + HapiMessageConverterHelper.createServiceRequest(patient, orderDateTime); + var messageHeader = HapiMessageConverterHelper.createOmlMessageHeader(); + var provenance = HapiMessageConverterHelper.createProvenanceResource(orderDateTime); bundle.getEntry().add(0, new Bundle.BundleEntryComponent().setResource(messageHeader)); bundle.addEntry(new Bundle.BundleEntryComponent().setResource(serviceRequest)); bundle.addEntry(new Bundle.BundleEntryComponent().setResource(provenance)); - - return resource; - } - - private MessageHeader createMessageHeader() { - var messageHeader = new MessageHeader(); - - messageHeader.setId(UUID.randomUUID().toString()); - - messageHeader.setEvent(OML_CODING); - - var meta = new Meta(); - - // Adding processing id of 'P' - meta.addTag("http://terminology.hl7.org/CodeSystem/v2-0103", "P", "Production"); - - messageHeader.setMeta(meta); - - messageHeader.setSource( - new MessageHeader.MessageSourceComponent( - new UrlType("https://reportstream.cdc.gov/")) - .setName("CDC Trusted Intermediary")); - - return messageHeader; - } - - private ServiceRequest createServiceRequest(final Patient patient, final Date orderDateTime) { - var serviceRequest = new ServiceRequest(); - - serviceRequest.setId(UUID.randomUUID().toString()); - - serviceRequest.setStatus(ServiceRequest.ServiceRequestStatus.ACTIVE); - - serviceRequest.setIntent(ServiceRequest.ServiceRequestIntent.ORDER); - - serviceRequest.setCode( - new CodeableConcept( - new Coding("http://loinc.org", "54089-8", "Newborn Screening Panel"))); - - serviceRequest.addCategory( - new CodeableConcept( - new Coding("http://snomed.info/sct", "108252007", "Laboratory procedure"))); - - serviceRequest.setSubject(new Reference(patient)); - - serviceRequest.setAuthoredOn(orderDateTime); - - return serviceRequest; - } - - private Provenance createProvenanceResource(Date orderDate) { - var provenance = new Provenance(); - - provenance.setId(UUID.randomUUID().toString()); - provenance.setRecorded(orderDate); - provenance.setActivity(new CodeableConcept(OML_CODING)); - - return provenance; } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java index 1515976d7..572ac6103 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java @@ -1,13 +1,21 @@ package gov.hhs.cdc.trustedintermediary.external.hapi; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; +import java.util.Date; +import java.util.List; +import java.util.UUID; import java.util.stream.Stream; import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Meta; import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Provenance; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.ServiceRequest; +import org.hl7.fhir.r4.model.UrlType; /** * Helper class with a variety of utilities to use on a FHIR bundle message. It adds the 'ETOR' tag @@ -16,6 +24,17 @@ */ public class HapiMessageConverterHelper { + private static final Coding OML_CODING = + new Coding( + "http://terminology.hl7.org/CodeSystem/v2-0003", + "O21", + "OML - Laboratory order"); + + private static final List CODING_LIST = + List.of( + new Coding( + "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "MTH", "mother")); + private static final HapiMessageConverterHelper INSTANCE = new HapiMessageConverterHelper(); public static HapiMessageConverterHelper getInstance() { @@ -81,4 +100,61 @@ public static Patient findPatientOrNull(Bundle bundle) { public static Stream findAllPatients(Bundle bundle) { return HapiHelper.resourcesInBundle(bundle, Patient.class); } + + public static MessageHeader createOmlMessageHeader() { + var messageHeader = new MessageHeader(); + + messageHeader.setId(UUID.randomUUID().toString()); + + messageHeader.setEvent(OML_CODING); + + var meta = new Meta(); + + // Adding processing id of 'P' + meta.addTag("http://terminology.hl7.org/CodeSystem/v2-0103", "P", "Production"); + + messageHeader.setMeta(meta); + + messageHeader.setSource( + new MessageHeader.MessageSourceComponent( + new UrlType("https://reportstream.cdc.gov/")) + .setName("CDC Trusted Intermediary")); + + return messageHeader; + } + + public static ServiceRequest createServiceRequest( + final Patient patient, final Date orderDateTime) { + var serviceRequest = new ServiceRequest(); + + serviceRequest.setId(UUID.randomUUID().toString()); + + serviceRequest.setStatus(ServiceRequest.ServiceRequestStatus.ACTIVE); + + serviceRequest.setIntent(ServiceRequest.ServiceRequestIntent.ORDER); + + serviceRequest.setCode( + new CodeableConcept( + new Coding("http://loinc.org", "54089-8", "Newborn Screening Panel"))); + + serviceRequest.addCategory( + new CodeableConcept( + new Coding("http://snomed.info/sct", "108252007", "Laboratory procedure"))); + + serviceRequest.setSubject(new Reference(patient)); + + serviceRequest.setAuthoredOn(orderDateTime); + + return serviceRequest; + } + + public static Provenance createProvenanceResource(Date orderDate) { + var provenance = new Provenance(); + + provenance.setId(UUID.randomUUID().toString()); + provenance.setRecorded(orderDate); + provenance.setActivity(new CodeableConcept(OML_CODING)); + + return provenance; + } } From a9867afd3ed6d5f913028c7ae91e693d07bf239a Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:25:57 -0700 Subject: [PATCH 055/151] Moved methods to more appropiate place --- .../transformation/custom/convertToOrder.java | 11 +-- .../hapi/HapiMessageConverterHelper.java | 98 ------------------- .../external/hapi/HapiOrderConverter.java | 23 +++-- 3 files changed, 19 insertions(+), 113 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java index dd946f9aa..7f6dad0ce 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java @@ -2,7 +2,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; import java.time.Instant; import java.util.Date; import java.util.Map; @@ -33,12 +33,11 @@ public void transform(FhirResource resource, Map args) { bundle.setType( Bundle.BundleType.MESSAGE); // it always needs to be a message, so no if statement - var patient = HapiMessageConverterHelper.findPatientOrNull(bundle); + var patient = HapiOrderConverter.findPatientOrNull(bundle); - var serviceRequest = - HapiMessageConverterHelper.createServiceRequest(patient, orderDateTime); - var messageHeader = HapiMessageConverterHelper.createOmlMessageHeader(); - var provenance = HapiMessageConverterHelper.createProvenanceResource(orderDateTime); + var serviceRequest = HapiOrderConverter.createServiceRequest(patient, orderDateTime); + var messageHeader = HapiOrderConverter.createOmlMessageHeader(); + var provenance = HapiOrderConverter.createProvenanceResource(orderDateTime); bundle.getEntry().add(0, new Bundle.BundleEntryComponent().setResource(messageHeader)); bundle.addEntry(new Bundle.BundleEntryComponent().setResource(serviceRequest)); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java index 572ac6103..ee867b52e 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java @@ -1,21 +1,11 @@ package gov.hhs.cdc.trustedintermediary.external.hapi; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; -import java.util.Date; -import java.util.List; -import java.util.UUID; -import java.util.stream.Stream; import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Meta; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Provenance; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.ServiceRequest; -import org.hl7.fhir.r4.model.UrlType; /** * Helper class with a variety of utilities to use on a FHIR bundle message. It adds the 'ETOR' tag @@ -24,17 +14,6 @@ */ public class HapiMessageConverterHelper { - private static final Coding OML_CODING = - new Coding( - "http://terminology.hl7.org/CodeSystem/v2-0003", - "O21", - "OML - Laboratory order"); - - private static final List CODING_LIST = - List.of( - new Coding( - "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "MTH", "mother")); - private static final HapiMessageConverterHelper INSTANCE = new HapiMessageConverterHelper(); public static HapiMessageConverterHelper getInstance() { @@ -80,81 +59,4 @@ public static MessageHeader findOrInitializeMessageHeader(Bundle bundle) { } return messageHeader; } - - /** - * Finds the first patient in bundle resource or returns null - * - * @param bundle - * @return Patient if found - */ - public static Patient findPatientOrNull(Bundle bundle) { - return HapiHelper.resourcesInBundle(bundle, Patient.class).findFirst().orElse(null); - } - - /** - * Finds all patient resources inside a given bundle - * - * @param bundle Bundle to check - * @return Stream list of patients. - */ - public static Stream findAllPatients(Bundle bundle) { - return HapiHelper.resourcesInBundle(bundle, Patient.class); - } - - public static MessageHeader createOmlMessageHeader() { - var messageHeader = new MessageHeader(); - - messageHeader.setId(UUID.randomUUID().toString()); - - messageHeader.setEvent(OML_CODING); - - var meta = new Meta(); - - // Adding processing id of 'P' - meta.addTag("http://terminology.hl7.org/CodeSystem/v2-0103", "P", "Production"); - - messageHeader.setMeta(meta); - - messageHeader.setSource( - new MessageHeader.MessageSourceComponent( - new UrlType("https://reportstream.cdc.gov/")) - .setName("CDC Trusted Intermediary")); - - return messageHeader; - } - - public static ServiceRequest createServiceRequest( - final Patient patient, final Date orderDateTime) { - var serviceRequest = new ServiceRequest(); - - serviceRequest.setId(UUID.randomUUID().toString()); - - serviceRequest.setStatus(ServiceRequest.ServiceRequestStatus.ACTIVE); - - serviceRequest.setIntent(ServiceRequest.ServiceRequestIntent.ORDER); - - serviceRequest.setCode( - new CodeableConcept( - new Coding("http://loinc.org", "54089-8", "Newborn Screening Panel"))); - - serviceRequest.addCategory( - new CodeableConcept( - new Coding("http://snomed.info/sct", "108252007", "Laboratory procedure"))); - - serviceRequest.setSubject(new Reference(patient)); - - serviceRequest.setAuthoredOn(orderDateTime); - - return serviceRequest; - } - - public static Provenance createProvenanceResource(Date orderDate) { - var provenance = new Provenance(); - - provenance.setId(UUID.randomUUID().toString()); - provenance.setRecorded(orderDate); - provenance.setActivity(new CodeableConcept(OML_CODING)); - - return provenance; - } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java index 0a2457d96..014b6838f 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java @@ -8,6 +8,7 @@ import java.util.Date; import java.util.List; import java.util.UUID; +import java.util.stream.Stream; import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CodeableConcept; @@ -79,7 +80,7 @@ public HapiOrder convertToOrder(final Demographics demographics) { .orElse(null); var serviceRequest = createServiceRequest(patient, orderDateTime); - var messageHeader = createMessageHeader(); + var messageHeader = createOmlMessageHeader(); var provenance = createProvenanceResource(orderDateTime); demographicsBundle @@ -132,9 +133,7 @@ public Order addContactSectionToPatientResource(Order order) { return new HapiOrder(orderBundle); } - private MessageHeader createMessageHeader() { - logger.logInfo("Creating new MessageHeader"); - + public static MessageHeader createOmlMessageHeader() { var messageHeader = new MessageHeader(); messageHeader.setId(UUID.randomUUID().toString()); @@ -166,9 +165,8 @@ public Order addEtorProcessingTag(Order message) { return new HapiOrder(messageBundle); } - private ServiceRequest createServiceRequest(final Patient patient, final Date orderDateTime) { - logger.logInfo("Creating new ServiceRequest"); - + public static ServiceRequest createServiceRequest( + final Patient patient, final Date orderDateTime) { var serviceRequest = new ServiceRequest(); serviceRequest.setId(UUID.randomUUID().toString()); @@ -192,8 +190,7 @@ private ServiceRequest createServiceRequest(final Patient patient, final Date or return serviceRequest; } - private Provenance createProvenanceResource(Date orderDate) { - logger.logInfo("Creating new Provenance"); + public static Provenance createProvenanceResource(Date orderDate) { var provenance = new Provenance(); provenance.setId(UUID.randomUUID().toString()); @@ -202,4 +199,12 @@ private Provenance createProvenanceResource(Date orderDate) { return provenance; } + + public static Patient findPatientOrNull(Bundle bundle) { + return HapiHelper.resourcesInBundle(bundle, Patient.class).findFirst().orElse(null); + } + + public static Stream findAllPatients(Bundle bundle) { + return HapiHelper.resourcesInBundle(bundle, Patient.class); + } } From 8d88fd124371017838a33ffd3d3739b06fcebb1d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:38:16 -0700 Subject: [PATCH 056/151] Fixed import --- .../custom/addContactSectionToPatientResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java index 418d48fb0..3bf484417 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java @@ -2,7 +2,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; import java.util.List; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; @@ -18,7 +18,7 @@ public class addContactSectionToPatientResource implements CustomFhirTransformat public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); - var patients = HapiMessageConverterHelper.findAllPatients(bundle); + var patients = HapiOrderConverter.findAllPatients(bundle); patients.forEach( p -> { From ab25de9bed0d5128dbb5470fb576a7c91bafd3f4 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:51:28 -0700 Subject: [PATCH 057/151] Using existing method and turning it static --- .../custom/addEtorProcessingTag.java | 15 +-------------- .../external/hapi/HapiMessageConverterHelper.java | 13 +------------ 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java index c8acf7c9d..0b31ea602 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java @@ -5,25 +5,12 @@ import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Meta; public class addEtorProcessingTag implements CustomFhirTransformation { @Override public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); - var messageHeader = HapiMessageConverterHelper.findOrInitializeMessageHeader(bundle); - var meta = messageHeader.hasMeta() ? messageHeader.getMeta() : new Meta(); - - var systemValue = "http://localcodes.org/ETOR"; - var codeValue = "ETOR"; - var displayValue = "Processed by ETOR"; - - if (meta.getTag(systemValue, codeValue) == null) { - meta.addTag(new Coding(systemValue, codeValue, displayValue)); - } - - messageHeader.setMeta(meta); + HapiMessageConverterHelper.addEtorTagToBundle(bundle); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java index ee867b52e..8fbf3a8ae 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java @@ -24,12 +24,7 @@ public static HapiMessageConverterHelper getInstance() { private HapiMessageConverterHelper() {} - /** - * Adds the `ETOR` code to any message provided - * - * @param messageBundle the in coming message in a FHIR bundle - */ - public void addEtorTagToBundle(Bundle messageBundle) { + public static void addEtorTagToBundle(Bundle messageBundle) { var messageHeader = findOrInitializeMessageHeader(messageBundle); var meta = messageHeader.hasMeta() ? messageHeader.getMeta() : new Meta(); @@ -44,12 +39,6 @@ public void addEtorTagToBundle(Bundle messageBundle) { messageHeader.setMeta(meta); } - /** - * Checks if the FHIR bundle has a messageHeader, and it creates one if it is missing - * - * @param bundle the in coming message in a FHIR bundle - * @return returns existing MessageHeader resource or a newly created one - */ public static MessageHeader findOrInitializeMessageHeader(Bundle bundle) { var messageHeader = HapiHelper.resourcesInBundle(bundle, MessageHeader.class).findFirst().orElse(null); From a0df1ed4837ac1372a55cecb2d09328aa6cf3c77 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 14:04:33 -0700 Subject: [PATCH 058/151] Replaced OrderConverter with transformation engine --- .../etor/orders/SendOrderUseCase.java | 17 +++------- .../etor/orders/SendOrderUseCaseTest.groovy | 33 ++++++------------- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java index 17356b9f3..edf3ed746 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java @@ -3,9 +3,9 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageHelper; import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageUseCase; import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; -import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import javax.inject.Inject; @@ -13,7 +13,7 @@ /** The overall logic to receive, convert to OML, and subsequently send a lab order. */ public class SendOrderUseCase implements SendMessageUseCase> { private static final SendOrderUseCase INSTANCE = new SendOrderUseCase(); - @Inject OrderConverter converter; + @Inject TransformationRuleEngine transformationEngine; @Inject OrderSender sender; @Inject MetricMetadata metadata; @Inject SendMessageHelper sendMessageHelper; @@ -42,18 +42,9 @@ public void convertAndSend(final Order order, String receivedSubmissionId) sendMessageHelper.savePartnerMetadataForReceivedMessage(partnerMetadata); - var omlOrder = converter.convertToOmlOrder(order); - metadata.put(order.getFhirResourceId(), EtorMetadataStep.ORDER_CONVERTED_TO_OML); + transformationEngine.runRules(order); - omlOrder = converter.addContactSectionToPatientResource(omlOrder); - metadata.put(order.getFhirResourceId(), EtorMetadataStep.CONTACT_SECTION_ADDED_TO_PATIENT); - - omlOrder = converter.addEtorProcessingTag(omlOrder); - metadata.put( - order.getFhirResourceId(), - EtorMetadataStep.ETOR_PROCESSING_TAG_ADDED_TO_MESSAGE_HEADER); - - String outboundReportId = sender.send(omlOrder).orElse(null); + String outboundReportId = sender.send(order).orElse(null); logger.logInfo("Sent order reportId: {}", outboundReportId); sendMessageHelper.linkMessage(receivedSubmissionId); diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy index 8ce8aa81e..42269f5b1 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy @@ -9,6 +9,7 @@ import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataOrchestrator +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata import spock.lang.Specification @@ -16,7 +17,7 @@ import spock.lang.Specification class SendOrderUseCaseTest extends Specification { def mockOrchestrator = Mock(PartnerMetadataOrchestrator) - def mockConverter = Mock(OrderConverter) + def engine = Mock(TransformationRuleEngine) def mockSender = Mock(OrderSender) def mockLogger = Mock(Logger) @@ -27,7 +28,7 @@ class SendOrderUseCaseTest extends Specification { TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.register(PartnerMetadataOrchestrator, mockOrchestrator) TestApplicationContext.register(SendMessageHelper, SendMessageHelper.getInstance()) - TestApplicationContext.register(OrderConverter, mockConverter) + TestApplicationContext.register(TransformationRuleEngine, engine) TestApplicationContext.register(OrderSender, mockSender) TestApplicationContext.register(Logger, mockLogger) } @@ -37,24 +38,16 @@ class SendOrderUseCaseTest extends Specification { def receivedSubmissionId = "receivedId" def sentSubmissionId = "sentId" def messagesIdsToLink = new HashSet<>(Set.of("messageId1", "messageId2")) - - def sendOrder = SendOrderUseCase.getInstance() def mockOrder = new OrderMock(null, null, null, null, null, null, null, null) - def mockOmlOrder = Mock(Order) TestApplicationContext.injectRegisteredImplementations() when: - sendOrder.convertAndSend(mockOrder, receivedSubmissionId) + SendOrderUseCase.getInstance().convertAndSend(mockOrder, receivedSubmissionId) then: - 1 * mockConverter.convertToOmlOrder(mockOrder) >> mockOmlOrder - 1 * mockConverter.addContactSectionToPatientResource(mockOmlOrder) >> mockOmlOrder - 1 * mockConverter.addEtorProcessingTag(mockOmlOrder) >> mockOmlOrder - 1 * mockSender.send(mockOmlOrder) >> Optional.of(sentSubmissionId) - 1 * sendOrder.metadata.put(_, EtorMetadataStep.ORDER_CONVERTED_TO_OML) - 1 * sendOrder.metadata.put(_, EtorMetadataStep.CONTACT_SECTION_ADDED_TO_PATIENT) - 1 * sendOrder.metadata.put(_, EtorMetadataStep.ETOR_PROCESSING_TAG_ADDED_TO_MESSAGE_HEADER) + 1 * engine.runRules(mockOrder) + 1 * mockSender.send(mockOrder) >> Optional.of(sentSubmissionId) 1 * mockOrchestrator.updateMetadataForReceivedMessage(_ as PartnerMetadata) 1 * mockOrchestrator.updateMetadataForSentMessage(receivedSubmissionId, sentSubmissionId) 1 * mockOrchestrator.findMessagesIdsToLink(receivedSubmissionId) >> messagesIdsToLink @@ -89,7 +82,6 @@ class SendOrderUseCaseTest extends Specification { def "convertAndSend logs error and continues when updateMetadataForReceivedOrder throws exception"() { given: def order = Mock(Order) - def omlOrder = Mock(Order) def receivedSubmissionId = "receivedId" mockOrchestrator.updateMetadataForReceivedMessage(_ as PartnerMetadata) >> { throw new PartnerMetadataException("Error") } @@ -100,17 +92,14 @@ class SendOrderUseCaseTest extends Specification { then: 1 * mockLogger.logError(_, _) - 1 * mockConverter.convertToOmlOrder(order) >> omlOrder - 1 * mockConverter.addContactSectionToPatientResource(omlOrder) >> omlOrder - 1 * mockConverter.addEtorProcessingTag(omlOrder) >> omlOrder + 1 * engine.runRules(order) 1 * mockOrchestrator.findMessagesIdsToLink(receivedSubmissionId) >> Set.of() - 1 * mockSender.send(omlOrder) >> Optional.of("sentId") + 1 * mockSender.send(order) >> Optional.of("sentId") } def "convertAndSend logs error and continues when updating the metadata for the sent order throws exception"() { given: def order = Mock(Order) - def omlOrder = Mock(Order) def partnerMetadataException = new PartnerMetadataException("Error") mockOrchestrator.updateMetadataForSentMessage("receivedId", _) >> { throw partnerMetadataException} TestApplicationContext.injectRegisteredImplementations() @@ -119,11 +108,9 @@ class SendOrderUseCaseTest extends Specification { SendOrderUseCase.getInstance().convertAndSend(order, "receivedId") then: - 1 * mockConverter.convertToOmlOrder(order) >> omlOrder - 1 * mockConverter.addContactSectionToPatientResource(omlOrder) >> omlOrder - 1 * mockConverter.addEtorProcessingTag(omlOrder) >> omlOrder + 1 * engine.runRules(order) 1 * mockOrchestrator.findMessagesIdsToLink(_ as String) >> Set.of() - 1 * mockSender.send(omlOrder) >> Optional.of("sentId") + 1 * mockSender.send(order) >> Optional.of("sentId") 1 * mockLogger.logError(_, partnerMetadataException) } From 614e1027502837396243de1e84d45b8abbb7919e Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 14:39:36 -0700 Subject: [PATCH 059/151] Replaced OrderConverter and ResultConverter with transformation engine --- .../etor/EtorDomainRegistration.java | 6 --- .../ConvertAndSendDemographicsUsecase.java | 8 ++-- .../etor/orders/OrderConverter.java | 14 ------ .../etor/results/ResultConverter.java | 6 --- .../etor/results/SendResultUseCase.java | 13 ++---- .../external/hapi/HapiOrderConverter.java | 43 +++---------------- .../external/hapi/HapiResultConverter.java | 36 ---------------- ...nvertAndSendDemographicsUsecaseTest.groovy | 2 +- .../PartnerMetadataOrchestratorTest.groovy | 2 +- .../hapi/HapiOrderConverterTest.groovy | 2 +- .../hapi/HapiResultConverterTest.groovy | 2 +- 11 files changed, 17 insertions(+), 117 deletions(-) delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderConverter.java delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/ResultConverter.java delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverter.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java index 760e16b0f..20215cd14 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java @@ -23,13 +23,11 @@ import gov.hhs.cdc.trustedintermediary.etor.operationoutcomes.FhirMetadata; import gov.hhs.cdc.trustedintermediary.etor.orders.Order; import gov.hhs.cdc.trustedintermediary.etor.orders.OrderController; -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter; import gov.hhs.cdc.trustedintermediary.etor.orders.OrderResponse; import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender; import gov.hhs.cdc.trustedintermediary.etor.orders.SendOrderUseCase; import gov.hhs.cdc.trustedintermediary.etor.results.Result; import gov.hhs.cdc.trustedintermediary.etor.results.ResultController; -import gov.hhs.cdc.trustedintermediary.etor.results.ResultConverter; import gov.hhs.cdc.trustedintermediary.etor.results.ResultResponse; import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender; import gov.hhs.cdc.trustedintermediary.etor.results.SendResultUseCase; @@ -42,9 +40,7 @@ import gov.hhs.cdc.trustedintermediary.external.database.PostgresDao; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiPartnerMetadataConverter; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiResultConverter; import gov.hhs.cdc.trustedintermediary.external.localfile.FileMessageLinkStorage; import gov.hhs.cdc.trustedintermediary.external.localfile.FilePartnerMetadataStorage; import gov.hhs.cdc.trustedintermediary.external.localfile.MockRSEndpointClient; @@ -112,12 +108,10 @@ public Map> domainRegistra ConvertAndSendDemographicsUsecase.class, ConvertAndSendDemographicsUsecase.getInstance()); // Orders - ApplicationContext.register(OrderConverter.class, HapiOrderConverter.getInstance()); ApplicationContext.register(OrderController.class, OrderController.getInstance()); ApplicationContext.register(SendOrderUseCase.class, SendOrderUseCase.getInstance()); ApplicationContext.register(OrderSender.class, ReportStreamOrderSender.getInstance()); // Results - ApplicationContext.register(ResultConverter.class, HapiResultConverter.getInstance()); ApplicationContext.register(ResultController.class, ResultController.getInstance()); ApplicationContext.register(SendResultUseCase.class, SendResultUseCase.getInstance()); ApplicationContext.register(ResultSender.class, ReportStreamResultSender.getInstance()); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java index a6965578c..5ce7f6b7e 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java @@ -2,8 +2,8 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; import gov.hhs.cdc.trustedintermediary.etor.orders.Order; -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter; import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; import javax.inject.Inject; /** @@ -15,7 +15,7 @@ public class ConvertAndSendDemographicsUsecase { private static final ConvertAndSendDemographicsUsecase INSTANCE = new ConvertAndSendDemographicsUsecase(); - @Inject OrderConverter converter; + @Inject TransformationRuleEngine transformationEngine; @Inject OrderSender sender; @@ -26,7 +26,7 @@ public static ConvertAndSendDemographicsUsecase getInstance() { private ConvertAndSendDemographicsUsecase() {} public void convertAndSend(Demographics demographics) throws UnableToSendMessageException { - Order order = converter.convertToOrder(demographics); - sender.send(order); + transformationEngine.runRules(demographics); + sender.send((Order) demographics); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderConverter.java deleted file mode 100644 index 684652130..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderConverter.java +++ /dev/null @@ -1,14 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.orders; - -import gov.hhs.cdc.trustedintermediary.etor.demographics.Demographics; - -/** Interface for converting things to orders and things in orders. */ -public interface OrderConverter { - Order convertToOrder(Demographics demographics); - - Order convertToOmlOrder(Order order); - - Order addContactSectionToPatientResource(Order order); - - Order addEtorProcessingTag(Order message); -} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/ResultConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/ResultConverter.java deleted file mode 100644 index d63603338..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/ResultConverter.java +++ /dev/null @@ -1,6 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.results; - -/** Interface for converting things to results and things in results. */ -public interface ResultConverter { - Result addEtorProcessingTag(Result message); -} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java index 9fe46db32..7f10d391d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java @@ -3,20 +3,18 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageHelper; import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageUseCase; import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; -import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; -import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import javax.inject.Inject; /** Use case for converting and sending a lab result message. */ public class SendResultUseCase implements SendMessageUseCase> { private static final SendResultUseCase INSTANCE = new SendResultUseCase(); - @Inject ResultConverter converter; + @Inject TransformationRuleEngine transformationEngine; @Inject ResultSender sender; - @Inject MetricMetadata metadata; @Inject SendMessageHelper sendMessageHelper; @@ -45,12 +43,9 @@ public void convertAndSend(Result result, String receivedSubmissionId) sendMessageHelper.savePartnerMetadataForReceivedMessage(partnerMetadata); - var convertedResult = converter.addEtorProcessingTag(result); - metadata.put( - result.getFhirResourceId(), - EtorMetadataStep.ETOR_PROCESSING_TAG_ADDED_TO_MESSAGE_HEADER); + transformationEngine.runRules(result); - String outboundReportId = sender.send(convertedResult).orElse(null); + String outboundReportId = sender.send(result).orElse(null); logger.logInfo("Sent result reportId: {}", outboundReportId); sendMessageHelper.linkMessage(receivedSubmissionId); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java index 014b6838f..aec1276dd 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java @@ -2,14 +2,11 @@ import gov.hhs.cdc.trustedintermediary.etor.demographics.Demographics; import gov.hhs.cdc.trustedintermediary.etor.orders.Order; -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter; -import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import java.time.Instant; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.stream.Stream; -import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; @@ -27,8 +24,7 @@ * Order }). Also converts an order to identify as an HL7v2 OML in the {@link * MessageHeader}. */ -public class HapiOrderConverter implements OrderConverter { - private static final HapiOrderConverter INSTANCE = new HapiOrderConverter(); +public class HapiOrderConverter { private static final Coding OML_CODING = new Coding( "http://terminology.hl7.org/CodeSystem/v2-0003", @@ -40,20 +36,7 @@ public class HapiOrderConverter implements OrderConverter { new Coding( "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "MTH", "mother")); - @Inject Logger logger; - - @Inject HapiMessageConverterHelper hapiMessageConverterHelper; - - public static HapiOrderConverter getInstance() { - return INSTANCE; - } - - private HapiOrderConverter() {} - - @Override - public HapiOrder convertToOrder(final Demographics demographics) { - logger.logInfo("Converting demographics to order"); - + public static HapiOrder convertToOrder(final Demographics demographics) { var hapiDemographics = (Demographics) demographics; var demographicsBundle = hapiDemographics.getUnderlyingResource(); @@ -92,23 +75,17 @@ public HapiOrder convertToOrder(final Demographics demographics) { return new HapiOrder(demographicsBundle); } - @Override - public Order convertToOmlOrder(Order order) { - logger.logInfo("Converting order to have OML metadata"); - + public static Order convertToOmlOrder(Order order) { var hapiOrder = (Order) order; var orderBundle = hapiOrder.getUnderlyingResource(); - var messageHeader = hapiMessageConverterHelper.findOrInitializeMessageHeader(orderBundle); + var messageHeader = HapiMessageConverterHelper.findOrInitializeMessageHeader(orderBundle); messageHeader.setEvent(OML_CODING); return new HapiOrder(orderBundle); } - @Override - public Order addContactSectionToPatientResource(Order order) { - logger.logInfo("Adding contact section in Patient resource"); - + public static Order addContactSectionToPatientResource(Order order) { var hapiOrder = (Order) order; var orderBundle = hapiOrder.getUnderlyingResource(); @@ -155,16 +132,6 @@ public static MessageHeader createOmlMessageHeader() { return messageHeader; } - @Override - public Order addEtorProcessingTag(Order message) { - var hapiOrder = (Order) message; - var messageBundle = hapiOrder.getUnderlyingResource(); - - hapiMessageConverterHelper.addEtorTagToBundle(messageBundle); - - return new HapiOrder(messageBundle); - } - public static ServiceRequest createServiceRequest( final Patient patient, final Date orderDateTime) { var serviceRequest = new ServiceRequest(); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverter.java deleted file mode 100644 index e7067a567..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverter.java +++ /dev/null @@ -1,36 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.external.hapi; - -import gov.hhs.cdc.trustedintermediary.etor.results.Result; -import gov.hhs.cdc.trustedintermediary.etor.results.ResultConverter; -import gov.hhs.cdc.trustedintermediary.wrappers.Logger; -import javax.inject.Inject; -import org.hl7.fhir.r4.model.Bundle; - -/** - * Converts {@link Result} to a Hapi-specific FHIR lab result {@link Result }) with proper - * identifying message headers. - */ -public class HapiResultConverter implements ResultConverter { - - private static final HapiResultConverter INSTANCE = new HapiResultConverter(); - - @Inject HapiMessageConverterHelper hapiMessageConverterHelper; - - @Inject Logger logger; - - public static HapiResultConverter getInstance() { - return INSTANCE; - } - - private HapiResultConverter() {} - - @Override - public Result addEtorProcessingTag(final Result message) { - var hapiResult = (Result) message; - var messageBundle = hapiResult.getUnderlyingResource(); - - hapiMessageConverterHelper.addEtorTagToBundle(messageBundle); - - return new HapiResult(messageBundle); - } -} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy index c1908bdbc..1a02f0eb5 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy @@ -3,7 +3,7 @@ package gov.hhs.cdc.trustedintermediary.etor.demographics import gov.hhs.cdc.trustedintermediary.DemographicsMock import gov.hhs.cdc.trustedintermediary.OrderMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter + import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender import spock.lang.Specification diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy index e91377139..e6a984d42 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy @@ -5,7 +5,7 @@ import gov.hhs.cdc.trustedintermediary.etor.RSEndpointClient import gov.hhs.cdc.trustedintermediary.etor.messagelink.MessageLink import gov.hhs.cdc.trustedintermediary.etor.messagelink.MessageLinkStorage import gov.hhs.cdc.trustedintermediary.etor.messages.MessageHdDataType -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter + import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamEndpointClientException diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index bbac342cb..36922204c 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -3,7 +3,7 @@ package gov.hhs.cdc.trustedintermediary.external.hapi import gov.hhs.cdc.trustedintermediary.DemographicsMock import gov.hhs.cdc.trustedintermediary.OrderMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter + import org.hl7.fhir.r4.model.Address import org.hl7.fhir.r4.model.ContactPoint import org.hl7.fhir.r4.model.Extension diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy index 42285b7f5..ccff37f3f 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy @@ -4,7 +4,7 @@ package gov.hhs.cdc.trustedintermediary.external.hapi import gov.hhs.cdc.trustedintermediary.ResultMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.results.Result -import gov.hhs.cdc.trustedintermediary.etor.results.ResultConverter + import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.MessageHeader import org.hl7.fhir.r4.model.Patient From 14ec0c27c1f4a75e70be7d9eb188564cdabdbbf5 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 14:40:02 -0700 Subject: [PATCH 060/151] Added back internal metadata logging --- .../custom/addContactSectionToPatientResource.java | 9 +++++++++ .../transformation/custom/addEtorProcessingTag.java | 7 +++++++ .../transformation/custom/convertToOmlOrder.java | 9 +++++++++ 3 files changed, 25 insertions(+) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java index 3bf484417..e860c6a92 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java @@ -1,14 +1,21 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; +import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; +import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; +import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import java.util.List; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Coding; public class addContactSectionToPatientResource implements CustomFhirTransformation { + + private final MetricMetadata metadata = + ApplicationContext.getImplementation(MetricMetadata.class); + private final List CODING_LIST = List.of( new Coding( @@ -35,5 +42,7 @@ public void transform(FhirResource resource, Map args) { myContact.setTelecom(p.getTelecom()); myContact.setAddress(p.getAddressFirstRep()); }); + + metadata.put(bundle.getId(), EtorMetadataStep.CONTACT_SECTION_ADDED_TO_PATIENT); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java index 0b31ea602..dd503d356 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java @@ -1,16 +1,23 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; +import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; +import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; +import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; public class addEtorProcessingTag implements CustomFhirTransformation { + private final MetricMetadata metadata = + ApplicationContext.getImplementation(MetricMetadata.class); + @Override public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); HapiMessageConverterHelper.addEtorTagToBundle(bundle); + metadata.put(bundle.getId(), EtorMetadataStep.ETOR_PROCESSING_TAG_ADDED_TO_MESSAGE_HEADER); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java index 5f8cd259c..b6f896eb9 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java @@ -2,13 +2,20 @@ import static gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper.findOrInitializeMessageHeader; +import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; +import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; +import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Coding; public class convertToOmlOrder implements CustomFhirTransformation { + + private final MetricMetadata metadata = + ApplicationContext.getImplementation(MetricMetadata.class); + private final Coding OML_CODING = new Coding( "http://terminology.hl7.org/CodeSystem/v2-0003", @@ -21,5 +28,7 @@ public void transform(FhirResource resource, Map args) { var messageHeader = findOrInitializeMessageHeader(bundle); messageHeader.setEvent(OML_CODING); + + metadata.put(bundle.getId(), EtorMetadataStep.ORDER_CONVERTED_TO_OML); } } From fd31ad5865e2b433c27d96231eb757ffd62fc48c Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:00:58 -0700 Subject: [PATCH 061/151] Reusing static methods in HapiOrderConverter --- .../addContactSectionToPatientResource.java | 27 +--------- .../custom/convertToOmlOrder.java | 15 +----- .../transformation/custom/convertToOrder.java | 32 +----------- .../external/hapi/HapiOrderConverter.java | 49 ++++++------------- .../hapi/HapiOrderConverterTest.groovy | 14 +++--- 5 files changed, 27 insertions(+), 110 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java index e860c6a92..3f55e99ad 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java @@ -6,43 +6,18 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; -import java.util.List; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Coding; public class addContactSectionToPatientResource implements CustomFhirTransformation { private final MetricMetadata metadata = ApplicationContext.getImplementation(MetricMetadata.class); - private final List CODING_LIST = - List.of( - new Coding( - "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "MTH", "mother")); - @Override public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); - - var patients = HapiOrderConverter.findAllPatients(bundle); - - patients.forEach( - p -> { - var myContact = p.addContact(); - var motherRelationship = myContact.addRelationship(); - motherRelationship.setCoding(CODING_LIST); - - var mothersMaidenNameExtension = - p.getExtensionByUrl( - "http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName"); - if (mothersMaidenNameExtension != null) { - myContact.setName(p.castToHumanName(mothersMaidenNameExtension.getValue())); - } - myContact.setTelecom(p.getTelecom()); - myContact.setAddress(p.getAddressFirstRep()); - }); - + HapiOrderConverter.addContactSectionToPatientResource(bundle); metadata.put(bundle.getId(), EtorMetadataStep.CONTACT_SECTION_ADDED_TO_PATIENT); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java index b6f896eb9..e5ce1d615 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java @@ -1,34 +1,23 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; -import static gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper.findOrInitializeMessageHeader; - import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Coding; public class convertToOmlOrder implements CustomFhirTransformation { private final MetricMetadata metadata = ApplicationContext.getImplementation(MetricMetadata.class); - private final Coding OML_CODING = - new Coding( - "http://terminology.hl7.org/CodeSystem/v2-0003", - "O21", - "OML - Laboratory order"); - @Override public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); - - var messageHeader = findOrInitializeMessageHeader(bundle); - messageHeader.setEvent(OML_CODING); - + HapiOrderConverter.convertToOmlOrder(bundle); metadata.put(bundle.getId(), EtorMetadataStep.ORDER_CONVERTED_TO_OML); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java index 7f6dad0ce..168a9eea8 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java @@ -3,44 +3,14 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; -import java.time.Instant; -import java.util.Date; import java.util.Map; -import java.util.UUID; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Identifier; public class convertToOrder implements CustomFhirTransformation { @Override public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); - - var overallId = UUID.randomUUID().toString(); - if (!bundle.hasId()) { - bundle.setId(overallId); - } - - if (!bundle.hasIdentifier()) { - bundle.setIdentifier(new Identifier().setValue(overallId)); - } - - var orderDateTime = Date.from(Instant.now()); - if (!bundle.hasTimestamp()) { - bundle.setTimestamp(orderDateTime); - } - - bundle.setType( - Bundle.BundleType.MESSAGE); // it always needs to be a message, so no if statement - - var patient = HapiOrderConverter.findPatientOrNull(bundle); - - var serviceRequest = HapiOrderConverter.createServiceRequest(patient, orderDateTime); - var messageHeader = HapiOrderConverter.createOmlMessageHeader(); - var provenance = HapiOrderConverter.createProvenanceResource(orderDateTime); - - bundle.getEntry().add(0, new Bundle.BundleEntryComponent().setResource(messageHeader)); - bundle.addEntry(new Bundle.BundleEntryComponent().setResource(serviceRequest)); - bundle.addEntry(new Bundle.BundleEntryComponent().setResource(provenance)); + HapiOrderConverter.convertDemographicsToOrder(bundle); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java index aec1276dd..cd81f3399 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java @@ -36,60 +36,45 @@ public class HapiOrderConverter { new Coding( "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "MTH", "mother")); - public static HapiOrder convertToOrder(final Demographics demographics) { - var hapiDemographics = (Demographics) demographics; - var demographicsBundle = hapiDemographics.getUnderlyingResource(); - + public static void convertDemographicsToOrder(Bundle demographics) { var overallId = UUID.randomUUID().toString(); - if (!demographicsBundle.hasId()) { - demographicsBundle.setId(overallId); + if (!demographics.hasId()) { + demographics.setId(overallId); } - if (!demographicsBundle.hasIdentifier()) { - demographicsBundle.setIdentifier(new Identifier().setValue(overallId)); + if (!demographics.hasIdentifier()) { + demographics.setIdentifier(new Identifier().setValue(overallId)); } var orderDateTime = Date.from(Instant.now()); - if (!demographicsBundle.hasTimestamp()) { - demographicsBundle.setTimestamp(orderDateTime); + if (!demographics.hasTimestamp()) { + demographics.setTimestamp(orderDateTime); } - demographicsBundle.setType( + demographics.setType( Bundle.BundleType.MESSAGE); // it always needs to be a message, so no if statement var patient = - HapiHelper.resourcesInBundle(demographicsBundle, Patient.class) - .findFirst() - .orElse(null); + HapiHelper.resourcesInBundle(demographics, Patient.class).findFirst().orElse(null); var serviceRequest = createServiceRequest(patient, orderDateTime); var messageHeader = createOmlMessageHeader(); var provenance = createProvenanceResource(orderDateTime); - demographicsBundle + demographics .getEntry() .add(0, new Bundle.BundleEntryComponent().setResource(messageHeader)); - demographicsBundle.addEntry(new Bundle.BundleEntryComponent().setResource(serviceRequest)); - demographicsBundle.addEntry(new Bundle.BundleEntryComponent().setResource(provenance)); - - return new HapiOrder(demographicsBundle); + demographics.addEntry(new Bundle.BundleEntryComponent().setResource(serviceRequest)); + demographics.addEntry(new Bundle.BundleEntryComponent().setResource(provenance)); } - public static Order convertToOmlOrder(Order order) { - var hapiOrder = (Order) order; - var orderBundle = hapiOrder.getUnderlyingResource(); - var messageHeader = HapiMessageConverterHelper.findOrInitializeMessageHeader(orderBundle); - + public static void convertToOmlOrder(Bundle order) { + var messageHeader = HapiMessageConverterHelper.findOrInitializeMessageHeader(order); messageHeader.setEvent(OML_CODING); - - return new HapiOrder(orderBundle); } - public static Order addContactSectionToPatientResource(Order order) { - var hapiOrder = (Order) order; - var orderBundle = hapiOrder.getUnderlyingResource(); - - HapiHelper.resourcesInBundle(orderBundle, Patient.class) + public static void addContactSectionToPatientResource(Bundle order) { + HapiHelper.resourcesInBundle(order, Patient.class) .forEach( p -> { var myContact = p.addContact(); @@ -106,8 +91,6 @@ public static Order addContactSectionToPatientResource(Order order) { myContact.setTelecom(p.getTelecom()); myContact.setAddress(p.getAddressFirstRep()); }); - - return new HapiOrder(orderBundle); } public static MessageHeader createOmlMessageHeader() { diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index 36922204c..f70491383 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -47,7 +47,7 @@ class HapiOrderConverterTest extends Specification { def "the converter fills in gaps of any missing data in the Bundle"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() then: orderBundle.hasId() @@ -67,7 +67,7 @@ class HapiOrderConverterTest extends Specification { mockDemographicsBundle.setTimestamp(mockTimestamp) when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() then: orderBundle.getId() == mockId @@ -81,7 +81,7 @@ class HapiOrderConverterTest extends Specification { mockDemographicsBundle.setType(Bundle.BundleType.COLLECTION) when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() then: orderBundle.getType() == Bundle.BundleType.MESSAGE @@ -90,7 +90,7 @@ class HapiOrderConverterTest extends Specification { def "the demographics correctly constructs a message header in the lab order"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() then: def messageHeader = orderBundle.getEntry().get(0).getResource() as MessageHeader @@ -108,7 +108,7 @@ class HapiOrderConverterTest extends Specification { def "the converter correctly reuses the patient from the passed in demographics"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() then: def patient = orderBundle.getEntry().get(1).getResource() as Patient @@ -119,7 +119,7 @@ class HapiOrderConverterTest extends Specification { def "the converter correctly constructs a service request in the lab order"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() then: def serviceRequest = orderBundle.getEntry().get(2).getResource() as ServiceRequest @@ -134,7 +134,7 @@ class HapiOrderConverterTest extends Specification { def "the order datetime should match for bundle, service request, and provenance resources"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() def bundleDateTime = orderBundle.getTimestamp() def serviceRequest = orderBundle.getEntry().get(2).getResource() as ServiceRequest def provenance = orderBundle.getEntry().get(3).getResource() as Provenance From ae1f2d81fafb7e8bc1456e24946f4c145c4695f2 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:04:02 -0700 Subject: [PATCH 062/151] Renamed method with more appropiate name --- .../{convertToOrder.java => convertDemographicsToOrder.java} | 2 +- etor/src/main/resources/transformation_definitions.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/{convertToOrder.java => convertDemographicsToOrder.java} (88%) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java similarity index 88% rename from etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java rename to etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java index 168a9eea8..43288369d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java @@ -6,7 +6,7 @@ import java.util.Map; import org.hl7.fhir.r4.model.Bundle; -public class convertToOrder implements CustomFhirTransformation { +public class convertDemographicsToOrder implements CustomFhirTransformation { @Override public void transform(FhirResource resource, Map args) { diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 550004854..17d7e57ad 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -12,7 +12,7 @@ ] }, { - "name": "convertToOrder", + "name": "convertDemographicsToOrder", "description": "Convert Demographics to Order", "message": "", "conditions": [ From b10522208744a7c1ed8d7fbb900964a020ada8eb Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 09:02:06 -0700 Subject: [PATCH 063/151] Fixed test for ConvertAndSendDemographicsUsecase --- .../ConvertAndSendDemographicsUsecase.java | 6 ++++-- .../ConvertAndSendDemographicsUsecaseTest.groovy | 16 ++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java index 5ce7f6b7e..43dceb0b4 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java @@ -1,10 +1,11 @@ package gov.hhs.cdc.trustedintermediary.etor.demographics; import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; -import gov.hhs.cdc.trustedintermediary.etor.orders.Order; import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrder; import javax.inject.Inject; +import org.hl7.fhir.r4.model.Bundle; /** * The overall logic that handles receiving patient demographics, converting it to a lab order, and @@ -27,6 +28,7 @@ private ConvertAndSendDemographicsUsecase() {} public void convertAndSend(Demographics demographics) throws UnableToSendMessageException { transformationEngine.runRules(demographics); - sender.send((Order) demographics); + var order = new HapiOrder((Bundle) demographics.getUnderlyingResource()); + sender.send(order); } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy index 1a02f0eb5..60a7a8439 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy @@ -1,11 +1,11 @@ package gov.hhs.cdc.trustedintermediary.etor.demographics import gov.hhs.cdc.trustedintermediary.DemographicsMock -import gov.hhs.cdc.trustedintermediary.OrderMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext - +import gov.hhs.cdc.trustedintermediary.etor.orders.Order import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender - +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper import spock.lang.Specification class ConvertAndSendDemographicsUsecaseTest extends Specification { @@ -13,16 +13,16 @@ class ConvertAndSendDemographicsUsecaseTest extends Specification { def setup() { TestApplicationContext.reset() TestApplicationContext.init() + TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) TestApplicationContext.register(ConvertAndSendDemographicsUsecase, ConvertAndSendDemographicsUsecase.getInstance()) } def "ConvertAndSend"() { given: - def mockOrder = new OrderMock(null, null, null, null, null, null, null, null) - def mockConverter = Mock(OrderConverter) + def engine = Mock(TransformationRuleEngine) def mockSender = Mock(OrderSender) - TestApplicationContext.register(OrderConverter, mockConverter) + TestApplicationContext.register(TransformationRuleEngine, engine) TestApplicationContext.register(OrderSender, mockSender) TestApplicationContext.injectRegisteredImplementations() @@ -32,7 +32,7 @@ class ConvertAndSendDemographicsUsecaseTest extends Specification { ConvertAndSendDemographicsUsecase.getInstance().convertAndSend(demographics) then: - 1 * mockConverter.convertToOrder(_ as Demographics) >> mockOrder - 1 * mockSender.send(mockOrder) + 1 * engine.runRules(_ as Demographics) + 1 * mockSender.send(_ as Order) } } From 511ec68d4b33805d2208ae6a65246d3078900131 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Wed, 1 May 2024 11:07:58 -0500 Subject: [PATCH 064/151] Start test coverage for existing transformation rules --- ...ansformationRuleEngineIntegrationTest.groovy | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy index 487a1ad0f..6dcb9b6d5 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy @@ -40,4 +40,21 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logError(_ as String, _ as Exception) } + + def "transformation rules filter and run rules for Orders"() { + given: + def bundle = new Bundle() + + when: + engine.runRules(new HapiFhirResource(bundle)) + + then: + 0 * mockLogger.logError(_ as String, _ as Exception) + } + + def "transformation rules filter and run rules for Results"() { + } + + def "transformation rules filter and run rules for Demographics"() { + } } From 3b6aae1fa878889f7ab2780f079b99fd8db5f91f Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 09:27:28 -0700 Subject: [PATCH 065/151] Fixed tests for SendOrderUseCase and renamed vartiables for consistency --- .../ConvertAndSendDemographicsUsecaseTest.groovy | 6 +++--- .../etor/orders/SendOrderUseCaseTest.groovy | 12 +++++------- .../etor/results/SendResultUseCaseTest.groovy | 11 ++++++----- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy index 60a7a8439..42f84a02c 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy @@ -19,10 +19,10 @@ class ConvertAndSendDemographicsUsecaseTest extends Specification { def "ConvertAndSend"() { given: - def engine = Mock(TransformationRuleEngine) + def mockEngine = Mock(TransformationRuleEngine) def mockSender = Mock(OrderSender) - TestApplicationContext.register(TransformationRuleEngine, engine) + TestApplicationContext.register(TransformationRuleEngine, mockEngine) TestApplicationContext.register(OrderSender, mockSender) TestApplicationContext.injectRegisteredImplementations() @@ -32,7 +32,7 @@ class ConvertAndSendDemographicsUsecaseTest extends Specification { ConvertAndSendDemographicsUsecase.getInstance().convertAndSend(demographics) then: - 1 * engine.runRules(_ as Demographics) + 1 * mockEngine.runRules(_ as Demographics) 1 * mockSender.send(_ as Order) } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy index 42269f5b1..f25f1b050 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy @@ -4,10 +4,8 @@ import gov.hhs.cdc.trustedintermediary.OrderMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageHelper import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException -import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException -import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataOrchestrator import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine import gov.hhs.cdc.trustedintermediary.wrappers.Logger @@ -17,7 +15,7 @@ import spock.lang.Specification class SendOrderUseCaseTest extends Specification { def mockOrchestrator = Mock(PartnerMetadataOrchestrator) - def engine = Mock(TransformationRuleEngine) + def mockEngine = Mock(TransformationRuleEngine) def mockSender = Mock(OrderSender) def mockLogger = Mock(Logger) @@ -28,7 +26,7 @@ class SendOrderUseCaseTest extends Specification { TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.register(PartnerMetadataOrchestrator, mockOrchestrator) TestApplicationContext.register(SendMessageHelper, SendMessageHelper.getInstance()) - TestApplicationContext.register(TransformationRuleEngine, engine) + TestApplicationContext.register(TransformationRuleEngine, mockEngine) TestApplicationContext.register(OrderSender, mockSender) TestApplicationContext.register(Logger, mockLogger) } @@ -46,7 +44,7 @@ class SendOrderUseCaseTest extends Specification { SendOrderUseCase.getInstance().convertAndSend(mockOrder, receivedSubmissionId) then: - 1 * engine.runRules(mockOrder) + 1 * mockEngine.runRules(mockOrder) 1 * mockSender.send(mockOrder) >> Optional.of(sentSubmissionId) 1 * mockOrchestrator.updateMetadataForReceivedMessage(_ as PartnerMetadata) 1 * mockOrchestrator.updateMetadataForSentMessage(receivedSubmissionId, sentSubmissionId) @@ -92,7 +90,7 @@ class SendOrderUseCaseTest extends Specification { then: 1 * mockLogger.logError(_, _) - 1 * engine.runRules(order) + 1 * mockEngine.runRules(order) 1 * mockOrchestrator.findMessagesIdsToLink(receivedSubmissionId) >> Set.of() 1 * mockSender.send(order) >> Optional.of("sentId") } @@ -108,7 +106,7 @@ class SendOrderUseCaseTest extends Specification { SendOrderUseCase.getInstance().convertAndSend(order, "receivedId") then: - 1 * engine.runRules(order) + 1 * mockEngine.runRules(order) 1 * mockOrchestrator.findMessagesIdsToLink(_ as String) >> Set.of() 1 * mockSender.send(order) >> Optional.of("sentId") 1 * mockLogger.logError(_, partnerMetadataException) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy index e7c6dbe9f..7cdf81404 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy @@ -9,6 +9,7 @@ import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataOrchestrator +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata import spock.lang.Specification @@ -16,7 +17,7 @@ import spock.lang.Specification class SendResultUseCaseTest extends Specification { def mockSender = Mock(ResultSender) - def mockConverter = Mock(ResultConverter) + def mockEngine = Mock(TransformationRuleEngine) def mockOrchestrator = Mock(PartnerMetadataOrchestrator) def mockLogger = Mock(Logger) @@ -27,7 +28,7 @@ class SendResultUseCaseTest extends Specification { TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.register(PartnerMetadataOrchestrator, mockOrchestrator) TestApplicationContext.register(SendMessageHelper, SendMessageHelper.getInstance()) - TestApplicationContext.register(ResultConverter, mockConverter) + TestApplicationContext.register(TransformationRuleEngine, mockEngine) TestApplicationContext.register(ResultSender, mockSender) TestApplicationContext.register(Logger, mockLogger) TestApplicationContext.injectRegisteredImplementations() @@ -42,7 +43,7 @@ class SendResultUseCaseTest extends Specification { SendResultUseCase.getInstance().convertAndSend(mockResult, receivedSubmissionId) then: - 1 * mockConverter.addEtorProcessingTag(mockResult) >> mockResult + 1 * mockEngine.runRules(mockResult) 1 * mockSender.send(mockResult) >> Optional.of("sentSubmissionId") } @@ -70,7 +71,7 @@ class SendResultUseCaseTest extends Specification { then: 1 * mockLogger.logError(_, _) - 1 * mockConverter.addEtorProcessingTag(result) >> result + 1 * mockEngine.runRules(result) 1 * mockSender.send(result) >> Optional.of("sentId") } @@ -85,7 +86,7 @@ class SendResultUseCaseTest extends Specification { SendResultUseCase.getInstance().convertAndSend(result, receivedSubmissionId) then: - 1 * mockConverter.addEtorProcessingTag(result) >> result + 1 * mockEngine.runRules(result) 1 * mockSender.send(result) >> Optional.of("sentId") 1 * mockLogger.logError(_, _) } From 775d8a6fb2c8ea10431294aee00c0e7d4dadfadc Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Wed, 1 May 2024 11:39:45 -0500 Subject: [PATCH 066/151] Fix PartnerMetadataOrchestratorTest --- .../metadata/partner/PartnerMetadataOrchestratorTest.groovy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy index e6a984d42..aa9020126 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy @@ -5,7 +5,7 @@ import gov.hhs.cdc.trustedintermediary.etor.RSEndpointClient import gov.hhs.cdc.trustedintermediary.etor.messagelink.MessageLink import gov.hhs.cdc.trustedintermediary.etor.messagelink.MessageLinkStorage import gov.hhs.cdc.trustedintermediary.etor.messages.MessageHdDataType - +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamEndpointClientException @@ -67,7 +67,6 @@ class PartnerMetadataOrchestratorTest extends Specification { TestApplicationContext.register(PartnerMetadataOrchestrator, PartnerMetadataOrchestrator.getInstance()) TestApplicationContext.register(MessageLinkStorage, mockMessageLinkStorage) - TestApplicationContext.register(OrderConverter, HapiOrderConverter.getInstance()) TestApplicationContext.register(PartnerMetadataStorage, mockPartnerMetadataStorage) TestApplicationContext.register(RSEndpointClient, mockClient) From c999565a61bab38ccc8567a6982163eec787a3fe Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 09:42:31 -0700 Subject: [PATCH 067/151] Removed unused HapiResultConverterTest --- .../hapi/HapiResultConverterTest.groovy | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy deleted file mode 100644 index ccff37f3f..000000000 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy +++ /dev/null @@ -1,55 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.external.hapi - - -import gov.hhs.cdc.trustedintermediary.ResultMock -import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.results.Result - -import org.hl7.fhir.r4.model.Bundle -import org.hl7.fhir.r4.model.MessageHeader -import org.hl7.fhir.r4.model.Patient -import spock.lang.Specification - -class HapiResultConverterTest extends Specification { - Bundle mockResultBundle - Patient mockPatient - Result mockResult - - def setup() { - TestApplicationContext.reset() - TestApplicationContext.init() - TestApplicationContext.register(ResultConverter, HapiResultConverter.getInstance()) - TestApplicationContext.register(HapiMessageConverterHelper, HapiMessageConverterHelper.getInstance()) - TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) - TestApplicationContext.injectRegisteredImplementations() - - mockPatient = new Patient() - mockResultBundle = new Bundle().addEntry(new Bundle.BundleEntryComponent().setResource(mockPatient)) - mockResult = new ResultMock("mockFhirResourceId", mockResultBundle, null, null, null, null, null) - } - - def "add etor processing tag to messageHeader resource"() { - given: - def expectedSystem = "http://localcodes.org/ETOR" - def expectedCode = "ETOR" - def expectedDisplay = "Processed by ETOR" - - def messageHeader = new MessageHeader() - messageHeader.setId(UUID.randomUUID().toString()) - def messageHeaderEntry = new Bundle.BundleEntryComponent().setResource(messageHeader) - mockResultBundle.getEntry().add(1, messageHeaderEntry) - mockResult.getUnderlyingResource() >> mockResultBundle - - when: - def convertedResultBundle = HapiResultConverter.getInstance().addEtorProcessingTag(mockResult).getUnderlyingResource() as Bundle - - then: - def messageHeaders = convertedResultBundle.getEntry().get(1).getResource() as MessageHeader - def actualSystem = messageHeaders.getMeta().getTag()[0].getSystem() - def actualCode = messageHeaders.getMeta().getTag()[0].getCode() - def actualDisplay = messageHeaders.getMeta().getTag()[0].getDisplay() - actualSystem == expectedSystem - actualCode == expectedCode - actualDisplay == expectedDisplay - } -} From fc2b07aa9c3f5128c8a277a8b90a21f4c0e2e3fd Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 09:42:58 -0700 Subject: [PATCH 068/151] removed unused import --- .../etor/results/SendResultUseCaseTest.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy index 7cdf81404..a4583ceba 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy @@ -7,7 +7,6 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageUseCase import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException -import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataOrchestrator import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine import gov.hhs.cdc.trustedintermediary.wrappers.Logger From 52c7dcfb77e8c055a1c1db698a3698e1810ae514 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Wed, 1 May 2024 11:43:28 -0500 Subject: [PATCH 069/151] Add baseline scenarios for TransformationRuleEngineIntegrationTest --- .../TransformationRuleEngineIntegrationTest.groovy | 8 -------- 1 file changed, 8 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy index 6dcb9b6d5..9048ef58a 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy @@ -42,14 +42,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { } def "transformation rules filter and run rules for Orders"() { - given: - def bundle = new Bundle() - - when: - engine.runRules(new HapiFhirResource(bundle)) - - then: - 0 * mockLogger.logError(_ as String, _ as Exception) } def "transformation rules filter and run rules for Results"() { From a0ee4729df871ead422ed7edfebdd42963e12e41 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 09:51:42 -0700 Subject: [PATCH 070/151] Removed convertDemographicsToOrder rule while figuring out the condition to apply it --- .../custom/convertDemographicsToOrder.java | 2 ++ .../main/resources/transformation_definitions.json | 14 -------------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java index 43288369d..c39ffe531 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java @@ -6,6 +6,8 @@ import java.util.Map; import org.hl7.fhir.r4.model.Bundle; +// todo: add this transformation entry in the transformation_definitions file once we have a +// condition to trigger it public class convertDemographicsToOrder implements CustomFhirTransformation { @Override diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 17d7e57ad..6fbbd07d2 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -11,20 +11,6 @@ } ] }, - { - "name": "convertDemographicsToOrder", - "description": "Convert Demographics to Order", - "message": "", - "conditions": [ - "Condition to filter demographics messages" - ], - "rules": [ - { - "name": "convertToOrder", - "args": { } - } - ] - }, { "name": "convertToOmlOrder", "description": "Convert ORM to OML Order", From b43b99a5a758bcc59d5f3a6c7453ff58fb11e418 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Wed, 1 May 2024 12:12:25 -0500 Subject: [PATCH 071/151] Start updating HapiOrderConverterTest --- .../hapi/HapiOrderConverterTest.groovy | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index f70491383..904b0348a 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -31,7 +31,6 @@ class HapiOrderConverterTest extends Specification { def setup() { TestApplicationContext.reset() TestApplicationContext.init() - TestApplicationContext.register(OrderConverter, HapiOrderConverter.getInstance()) TestApplicationContext.register(HapiMessageConverterHelper, HapiMessageConverterHelper.getInstance()) TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) TestApplicationContext.injectRegisteredImplementations() @@ -47,14 +46,14 @@ class HapiOrderConverterTest extends Specification { def "the converter fills in gaps of any missing data in the Bundle"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() + HapiOrderConverter.convertDemographicsToOrder(mockDemographics as Bundle) then: - orderBundle.hasId() - orderBundle.hasIdentifier() - orderBundle.hasTimestamp() - orderBundle.getType() == Bundle.BundleType.MESSAGE - orderBundle.getId() == orderBundle.getIdentifier().getValue() + mockDemographics + mockDemographics.hasIdentifier() + mockDemographics.hasTimestamp() + mockDemographics.getType() == Bundle.BundleType.MESSAGE + mockDemographics.getId() == mockDemographics.getIdentifier().getValue() } def "the converter doesn't change things if it is already set"() { @@ -67,7 +66,7 @@ class HapiOrderConverterTest extends Specification { mockDemographicsBundle.setTimestamp(mockTimestamp) when: - def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) then: orderBundle.getId() == mockId @@ -81,7 +80,7 @@ class HapiOrderConverterTest extends Specification { mockDemographicsBundle.setType(Bundle.BundleType.COLLECTION) when: - def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) then: orderBundle.getType() == Bundle.BundleType.MESSAGE @@ -90,7 +89,7 @@ class HapiOrderConverterTest extends Specification { def "the demographics correctly constructs a message header in the lab order"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) then: def messageHeader = orderBundle.getEntry().get(0).getResource() as MessageHeader @@ -108,7 +107,7 @@ class HapiOrderConverterTest extends Specification { def "the converter correctly reuses the patient from the passed in demographics"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) then: def patient = orderBundle.getEntry().get(1).getResource() as Patient @@ -119,7 +118,7 @@ class HapiOrderConverterTest extends Specification { def "the converter correctly constructs a service request in the lab order"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) then: def serviceRequest = orderBundle.getEntry().get(2).getResource() as ServiceRequest @@ -134,7 +133,7 @@ class HapiOrderConverterTest extends Specification { def "the order datetime should match for bundle, service request, and provenance resources"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertDemographicsToOrder(mockDemographics).getUnderlyingResource() + def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) def bundleDateTime = orderBundle.getTimestamp() def serviceRequest = orderBundle.getEntry().get(2).getResource() as ServiceRequest def provenance = orderBundle.getEntry().get(3).getResource() as Provenance @@ -158,22 +157,26 @@ class HapiOrderConverterTest extends Specification { "ORM")))) when: - def convertedOrderBundle = HapiOrderConverter.getInstance().convertToOmlOrder(mockOrder).getUnderlyingResource() as Bundle + HapiOrderConverter.convertToOmlOrder(mockOrder.getUnderlyingResource()) then: - def convertedMessageHeader = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader + def convertedMessageHeader = + HapiHelper.resourcesInBundle(mockOrder.getUnderlyingResource(), MessageHeader.class).findFirst().orElse(null) + convertedMessageHeader != null convertedMessageHeader.getEventCoding().getCode() == "O21" convertedMessageHeader.getEventCoding().getDisplay().contains("OML") } def "adds the message header to specify OML"() { when: - def convertedOrderBundle = HapiOrderConverter.getInstance().convertToOmlOrder(mockOrder).getUnderlyingResource() as Bundle + HapiOrderConverter.convertToOmlOrder(mockOrder.getUnderlyingResource()) then: - def convertedMessageHeader = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader + def convertedMessageHeader = + HapiHelper.resourcesInBundle(mockOrder.getUnderlyingResource(), MessageHeader.class).findFirst().orElse(null) + convertedMessageHeader != null convertedMessageHeader.getEventCoding().getCode() == "O21" convertedMessageHeader.getEventCoding().getDisplay().contains("OML") } @@ -192,10 +195,10 @@ class HapiOrderConverterTest extends Specification { mockOrderBundle.setEntry(entryList) when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addContactSectionToPatientResource(mockOrder).getUnderlyingResource() as Bundle + HapiOrderConverter.addContactSectionToPatientResource(mockOrder.getUnderlyingResource()) then: - def convertedPatient = convertedOrderBundle.getEntry().get(0).getResource() as Patient + def convertedPatient = HapiHelper.resourcesInBundle(mockOrder.getUnderlyingResource(), Patient.class).findFirst().orElse(null) def contactSection = convertedPatient.getContact()[0] contactSection != null From 85efd288db845f02186d4595afcb0d3a1fa171ca Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 10:32:03 -0700 Subject: [PATCH 072/151] Fixed HapiOrderConverter tests for convertDemographicsToOrder --- .../hapi/HapiOrderConverterTest.groovy | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index 904b0348a..caa341551 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -44,20 +44,23 @@ class HapiOrderConverterTest extends Specification { } def "the converter fills in gaps of any missing data in the Bundle"() { + given: + def mockDemographicsResource = mockDemographics.getUnderlyingResource() when: - HapiOrderConverter.convertDemographicsToOrder(mockDemographics as Bundle) + HapiOrderConverter.convertDemographicsToOrder(mockDemographicsResource) then: - mockDemographics - mockDemographics.hasIdentifier() - mockDemographics.hasTimestamp() - mockDemographics.getType() == Bundle.BundleType.MESSAGE - mockDemographics.getId() == mockDemographics.getIdentifier().getValue() + mockDemographicsResource + mockDemographicsResource.hasIdentifier() + mockDemographicsResource.hasTimestamp() + mockDemographicsResource.getType() == Bundle.BundleType.MESSAGE + mockDemographicsResource.getId() == mockDemographicsResource.getIdentifier().getValue() } def "the converter doesn't change things if it is already set"() { given: + def mockDemographicsResource = mockDemographics.getUnderlyingResource() def mockId = "an id" mockDemographicsBundle.setId(mockId) def mockIdentifier = "an identifier" @@ -66,34 +69,36 @@ class HapiOrderConverterTest extends Specification { mockDemographicsBundle.setTimestamp(mockTimestamp) when: - def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) + HapiOrderConverter.convertDemographicsToOrder(mockDemographicsResource) then: - orderBundle.getId() == mockId - orderBundle.getIdentifier().getValue() == mockIdentifier - orderBundle.getTimestamp() == mockTimestamp - orderBundle.getId() != orderBundle.getIdentifier().getValue() + mockDemographicsResource.getId() == mockId + mockDemographicsResource.getIdentifier().getValue() == mockIdentifier + mockDemographicsResource.getTimestamp() == mockTimestamp + mockDemographicsResource.getId() != mockDemographicsResource.getIdentifier().getValue() } def "the converter always changes the bundle type to message"() { given: + def mockDemographicsResource = mockDemographics.getUnderlyingResource() mockDemographicsBundle.setType(Bundle.BundleType.COLLECTION) when: - def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) + HapiOrderConverter.convertDemographicsToOrder(mockDemographicsResource) then: - orderBundle.getType() == Bundle.BundleType.MESSAGE + mockDemographicsResource.getType() == Bundle.BundleType.MESSAGE } def "the demographics correctly constructs a message header in the lab order"() { + given: + def mockDemographicsResource = mockDemographics.getUnderlyingResource() when: - def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) + HapiOrderConverter.convertDemographicsToOrder(mockDemographicsResource) + def messageHeader = HapiHelper.resourcesInBundle(mockDemographicsResource, MessageHeader.class).findFirst().orElse(null) then: - def messageHeader = orderBundle.getEntry().get(0).getResource() as MessageHeader - messageHeader.hasId() messageHeader.getMeta().getTag().system[0] == "http://terminology.hl7.org/CodeSystem/v2-0103" messageHeader.getMeta().getTag().code[0] == "P" @@ -105,43 +110,46 @@ class HapiOrderConverterTest extends Specification { } def "the converter correctly reuses the patient from the passed in demographics"() { + given: + def mockDemographicsResource = mockDemographics.getUnderlyingResource() when: - def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) + HapiOrderConverter.convertDemographicsToOrder(mockDemographicsResource) + def patient = HapiHelper.resourcesInBundle(mockDemographicsResource, Patient.class).findFirst().orElse(null) then: - def patient = orderBundle.getEntry().get(1).getResource() as Patient - patient == mockPatient } def "the converter correctly constructs a service request in the lab order"() { + given: + def mockDemographicsResource = mockDemographics.getUnderlyingResource() when: - def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) + HapiOrderConverter.convertDemographicsToOrder(mockDemographicsResource) + def serviceRequest = HapiHelper.resourcesInBundle(mockDemographicsResource, ServiceRequest.class).findFirst().orElse(null) then: - def serviceRequest = orderBundle.getEntry().get(2).getResource() as ServiceRequest - serviceRequest.hasId() serviceRequest.getCode().getCodingFirstRep().getCode() == "54089-8" serviceRequest.getCategoryFirstRep().getCodingFirstRep().getCode() == "108252007" - serviceRequest.getSubject().getResource() == orderBundle.getEntry().get(1).getResource() + serviceRequest.getSubject().getResource() == mockDemographicsResource.getEntry().get(1).getResource() serviceRequest.hasAuthoredOn() } def "the order datetime should match for bundle, service request, and provenance resources"() { + given: + def mockDemographicsResource = mockDemographics.getUnderlyingResource() when: - def orderBundle = HapiOrderConverter.convertDemographicsToOrder(mockDemographics) - def bundleDateTime = orderBundle.getTimestamp() - def serviceRequest = orderBundle.getEntry().get(2).getResource() as ServiceRequest - def provenance = orderBundle.getEntry().get(3).getResource() as Provenance - - then: + HapiOrderConverter.convertDemographicsToOrder(mockDemographicsResource) + def bundleDateTime = mockDemographicsResource.getTimestamp() + def serviceRequest = HapiHelper.resourcesInBundle(mockDemographicsResource, ServiceRequest.class).findFirst().orElse(null) + def provenance = HapiHelper.resourcesInBundle(mockDemographicsResource, Provenance.class).findFirst().orElse(null) def serviceRequestDateTime = serviceRequest.getAuthoredOn() def provenanceDateTime = provenance.getRecorded() + then: bundleDateTime == serviceRequestDateTime bundleDateTime == provenanceDateTime serviceRequestDateTime == provenanceDateTime From 31513847a14205774f2046f5db5e62034c76be98 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Wed, 1 May 2024 13:33:06 -0400 Subject: [PATCH 073/151] Update HapiOrderConverterTest.groovy Fixed contact section test --- .../external/hapi/HapiOrderConverterTest.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index caa341551..f25d4703b 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -272,11 +272,11 @@ class HapiOrderConverterTest extends Specification { entryList.add(patientEntry) mockOrderBundle.setEntry(entryList) when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addContactSectionToPatientResource(mockOrder).getUnderlyingResource() as Bundle + HapiOrderConverter.addContactSectionToPatientResource(mockOrderBundle) then: - def convertedPatient = convertedOrderBundle.getEntry().get(0).getResource() as Patient - def contactSection = convertedPatient.getContact()[0] + def convertedPatient = HapiHelper.resourcesInBundle(mockOrderBundle, Patient.class).findFirst().orElse(null) + def contactSection = convertedPatient.getContact().first() !contactSection.hasName() } From 939495b492e820b889f4b3df249fd6360828e293 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 10:38:13 -0700 Subject: [PATCH 074/151] Added missing MetricMetadata mock to fix test --- .../ruleengine/TransformationRuleEngineIntegrationTest.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy index 9048ef58a..7eca000c1 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy @@ -8,6 +8,7 @@ import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.Logger +import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import org.hl7.fhir.r4.model.Bundle import spock.lang.Specification @@ -26,6 +27,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.register(TransformationRuleEngine, engine) TestApplicationContext.register(RuleLoader, RuleLoader.getInstance()) TestApplicationContext.register(Logger, mockLogger) + TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.injectRegisteredImplementations() } From 7a6575455e9826f17325bd90a1d99c87af30d80f Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Wed, 1 May 2024 13:40:06 -0400 Subject: [PATCH 075/151] Update HapiOrderConverterTest.groovy Remove test that was moved elsewhere --- .../hapi/HapiOrderConverterTest.groovy | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index f25d4703b..5e9f96b02 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -237,32 +237,6 @@ class HapiOrderConverterTest extends Specification { contactSectionAddress.getPostalCode() == convertedPatientAddress.getPostalCode() } - def "add etor processing tag to messageHeader resource"() { - given: - def expectedSystem = "http://localcodes.org/ETOR" - def expectedCode = "ETOR" - def expectedDisplay = "Processed by ETOR" - - def messageHeader = new MessageHeader() - messageHeader.setId(UUID.randomUUID().toString()) - def messageHeaderEntry = new Bundle.BundleEntryComponent().setResource(messageHeader) - mockOrderBundle.getEntry().add(1, messageHeaderEntry) - mockOrder.getUnderlyingResource() >> mockOrderBundle - - when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addEtorProcessingTag(mockOrder).getUnderlyingResource() as Bundle - - then: - def messageHeaders = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader - def actualSystem = messageHeaders.getMeta().getTag()[0].getSystem() - def actualCode = messageHeaders.getMeta().getTag()[0].getCode() - def actualDisplay = messageHeaders.getMeta().getTag()[0].getDisplay() - actualSystem == expectedSystem - actualCode == expectedCode - actualDisplay == expectedDisplay - } - - def "no humanName section in contact"() { given: def addHumanName = false From ecbd9e23e9ed3fc54836dd070ed06d7da079445b Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 13:31:00 -0700 Subject: [PATCH 076/151] Fixed loading of resource definitions file in engine --- .../etor/ruleengine/RuleLoader.java | 10 ++++------ .../TransformationRuleEngine.java | 17 +++++++---------- .../validation/ValidationRuleEngine.java | 17 +++++++---------- .../etor/ruleengine/RuleLoaderTest.groovy | 4 ++-- .../ruleengine/ValidationRuleEngineTest.groovy | 18 +++++++++--------- 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java index 7992a01d6..1fad40553 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoader.java @@ -7,8 +7,6 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Map; @@ -26,17 +24,17 @@ public static RuleLoader getInstance() { return INSTANCE; } - public List loadRules(Path path, TypeReference>> typeReference) + public List loadRules( + InputStream ruleDefinitionStream, TypeReference>> typeReference) throws RuleLoaderException { - try (InputStream ruleDefinitionStream = Files.newInputStream(path)) { + try { var rulesString = new String(ruleDefinitionStream.readAllBytes(), StandardCharsets.UTF_8); Map> jsonObj = formatter.convertJsonToObject(rulesString, typeReference); return jsonObj.getOrDefault("definitions", Collections.emptyList()); } catch (IOException | FormatterProcessingException e) { - throw new RuleLoaderException( - "Failed to load rules definitions for provided path: " + path, e); + throw new RuleLoaderException("Failed to load rules definitions", e); } } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index bbaec712c..ad82afa45 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -6,8 +6,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -39,15 +38,13 @@ public void ensureRulesLoaded() throws RuleLoaderException { if (rules.isEmpty()) { synchronized (this) { if (rules.isEmpty()) { - Path path = - Paths.get( - getClass() - .getClassLoader() - .getResource(ruleDefinitionsFileName) - .getPath()); - + InputStream resourceStream = + getClass() + .getClassLoader() + .getResourceAsStream(ruleDefinitionsFileName); + assert resourceStream != null; List parsedRules = - ruleLoader.loadRules(path, new TypeReference<>() {}); + ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); this.rules.addAll(parsedRules); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java index 8554a6394..45f15abb9 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java @@ -6,8 +6,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -39,15 +38,13 @@ public void ensureRulesLoaded() throws RuleLoaderException { if (rules.isEmpty()) { synchronized (this) { if (rules.isEmpty()) { - Path path = - Paths.get( - getClass() - .getClassLoader() - .getResource(ruleDefinitionsFileName) - .getPath()); - + InputStream resourceStream = + getClass() + .getClassLoader() + .getResourceAsStream(ruleDefinitionsFileName); + assert resourceStream != null; List parsedRules = - ruleLoader.loadRules(path, new TypeReference<>() {}); + ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); this.rules.addAll(parsedRules); } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy index 85edab9fe..cd66dc08d 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderTest.groovy @@ -49,7 +49,7 @@ class RuleLoaderTest extends Specification { Files.writeString(tempFile, fileContents) when: - List rules = RuleLoader.getInstance().loadRules(tempFile, new TypeReference>>() {}) + List rules = RuleLoader.getInstance().loadRules(Files.newInputStream(tempFile), new TypeReference>>() {}) then: rules.size() == 1 @@ -68,7 +68,7 @@ class RuleLoaderTest extends Specification { Files.writeString(tempFile, "!K@WJ#8uhy") when: - RuleLoader.getInstance().loadRules(tempFile, new TypeReference>>() {}) + RuleLoader.getInstance().loadRules(Files.newInputStream(tempFile), new TypeReference>>() {}) then: thrown(RuleLoaderException) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy index e52261f2d..1083b95fa 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy @@ -29,26 +29,26 @@ class ValidationRuleEngineTest extends Specification { def "ensureRulesLoaded happy path"() { given: - mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] when: ruleEngine.ensureRulesLoaded() then: - 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] + 1 * mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] ruleEngine.rules.size() == 1 } def "ensureRulesLoaded loads rules only once by default"() { given: - mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] when: ruleEngine.ensureRulesLoaded() ruleEngine.ensureRulesLoaded() // Call twice to test if rules are loaded only once then: - 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] + 1 * mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] ruleEngine.rules.size() == 1 } @@ -58,7 +58,7 @@ class ValidationRuleEngineTest extends Specification { def iterations = 4 when: - mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [mockRule] + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] List threads = [] (1..threadsNum).each { threadId -> threads.add(new Thread({ @@ -71,13 +71,13 @@ class ValidationRuleEngineTest extends Specification { threads*.join() then: - 1 * mockRuleLoader.loadRules(_ as Path, _ as TypeReference) + 1 * mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) } def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) - mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> { + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> { mockLogger.logError("Error loading rules", exception) return [] } @@ -97,7 +97,7 @@ class ValidationRuleEngineTest extends Specification { def invalidRule = Mock(ValidationRule) invalidRule.getMessage() >> failedValidationMessage invalidRule.shouldRun(fhirBundle) >> true - mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> [invalidRule] + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [invalidRule] when: invalidRule.runRule(fhirBundle) >> { @@ -128,7 +128,7 @@ class ValidationRuleEngineTest extends Specification { def "runRules logs an error and doesn't run any rules when there's a RuleLoaderException"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) - mockRuleLoader.loadRules(_ as Path, _ as TypeReference) >> { throw exception } + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> { throw exception } when: ruleEngine.runRules(Mock(FhirResource)) From fd76553020c54c0e2506d99fb554929430962527 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 14:09:37 -0700 Subject: [PATCH 077/151] Fixed path reference to custom transformation classes --- .../etor/ruleengine/transformation/TransformationRule.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index b2deac3e4..2e741b16f 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -60,7 +60,8 @@ private static Class loadCustomTransformationClassFromFile(String className) "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; Path customTransformationPath = Paths.get( - "src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/"); + System.getProperty("user.dir"), + "../etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/"); File[] customTransformationFiles = customTransformationPath.toFile().listFiles(); assert customTransformationFiles != null; From 98316374b5de0700ee34199e30ae441d09ac4d75 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 14:16:16 -0700 Subject: [PATCH 078/151] Ignoring test for now --- .../e2e/DemographicsTest.groovy | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy b/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy index 389650e6d..7c58f063d 100644 --- a/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy +++ b/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy @@ -29,24 +29,25 @@ class DemographicsTest extends Specification { parsedJsonBody.patientId == expectedPatientId } - def "payload file check"() { - when: - def response = demographicsClient.submit(newbornPatientJsonFileString, true) - def parsedResponseBody = JsonParser.parseContent(response) - def sentPayload = SentPayloadReader.read() - def parsedSentPayload = JsonParser.parse(sentPayload) - - then: - response.getCode() == 200 - parsedSentPayload.entry[0].resource.resourceType == "MessageHeader" - parsedSentPayload.entry[2].resource.resourceType == "ServiceRequest" - - parsedSentPayload.entry[1].resource.resourceType == "Patient" - parsedSentPayload.entry[1].resource.id == "infant-twin-1" - - parsedSentPayload.entry[1].resource.identifier[1].value == parsedResponseBody.patientId //the second (index 1) identifier so happens to be the MRN - parsedSentPayload.resourceType + "/" + parsedSentPayload.id == parsedResponseBody.fhirResourceId - } + // Commenting out this temporarily while figuring out demographics condition to be able to add rule + // def "payload file check"() { + // when: + // def response = demographicsClient.submit(newbornPatientJsonFileString, true) + // def parsedResponseBody = JsonParser.parseContent(response) + // def sentPayload = SentPayloadReader.read() + // def parsedSentPayload = JsonParser.parse(sentPayload) + // + // then: + // response.getCode() == 200 + // parsedSentPayload.entry[0].resource.resourceType == "MessageHeader" + // parsedSentPayload.entry[2].resource.resourceType == "ServiceRequest" + // + // parsedSentPayload.entry[1].resource.resourceType == "Patient" + // parsedSentPayload.entry[1].resource.id == "infant-twin-1" + // + // parsedSentPayload.entry[1].resource.identifier[1].value == parsedResponseBody.patientId //the second (index 1) identifier so happens to be the MRN + // parsedSentPayload.resourceType + "/" + parsedSentPayload.id == parsedResponseBody.fhirResourceId + // } def "return a 400 response when request has unexpected format"() { given: From 60c046a505aa458cbb54b6daa4799ac51139a46e Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 14:35:57 -0700 Subject: [PATCH 079/151] Moved tests to correct location --- .../TransformationRuleEngineIntegrationTest.groovy | 5 ++--- .../ValidationRuleEngineIntegrationTest.groovy | 5 ++--- .../{ => validation}/ValidationRuleEngineTest.groovy | 10 +++++----- .../{ => validation}/ValidationRuleTest.groovy | 4 ++-- 4 files changed, 11 insertions(+), 13 deletions(-) rename etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => transformation}/TransformationRuleEngineIntegrationTest.groovy (88%) rename etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => validation}/ValidationRuleEngineIntegrationTest.groovy (97%) rename etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => validation}/ValidationRuleEngineTest.groovy (92%) rename etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/{ => validation}/ValidationRuleTest.groovy (96%) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy similarity index 88% rename from etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy rename to etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 7eca000c1..33943c714 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,8 +1,7 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy similarity index 97% rename from etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy rename to etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy index 001ce7bff..0cc2d78f1 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy @@ -1,8 +1,7 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy similarity index 92% rename from etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy rename to etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy index 1083b95fa..6178b5af9 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy @@ -1,14 +1,14 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRuleEngine +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference import spock.lang.Specification -import java.nio.file.Path - class ValidationRuleEngineTest extends Specification { def ruleEngine = ValidationRuleEngine.getInstance("validation_definitions.json") def mockRuleLoader = Mock(RuleLoader) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleTest.groovy similarity index 96% rename from etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy rename to etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleTest.groovy index b6fb44ad6..f2877557a 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleTest.groovy @@ -1,8 +1,8 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation import gov.hhs.cdc.trustedintermediary.FhirResourceMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.Logger From 4379229f67c02faaa26f75d15ea36ea887072523 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 15:03:27 -0700 Subject: [PATCH 080/151] Added empty tests to work on --- ...sformationRuleEngineIntegrationTest.groovy | 11 +++++---- .../TransformationRuleEngineTest.groovy | 24 +++++++++++++++++++ .../TransformationRuleTest.groovy | 17 +++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 33943c714..d5cbc35c6 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -12,9 +12,9 @@ import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import org.hl7.fhir.r4.model.Bundle import spock.lang.Specification -class TransformationRuleEngineIntegrationTest extends Specification { - def fhir = HapiFhirImplementation.getInstance() +class TransformationRuleEngineIntegrationTest extends Specification { def engine = TransformationRuleEngine.getInstance("transformation_definitions.json") + def fhir = HapiFhirImplementation.getInstance() def mockLogger = Mock(Logger) def setup() { @@ -42,10 +42,13 @@ class TransformationRuleEngineIntegrationTest extends Specification { 0 * mockLogger.logError(_ as String, _ as Exception) } - def "transformation rules filter and run rules for Orders"() { + def "transformation rules filter and run rules for ORM messages"() { + } + + def "transformation rules filter and run rules for OML messages"() { } - def "transformation rules filter and run rules for Results"() { + def "transformation rules filter and run rules for ORU messages"() { } def "transformation rules filter and run rules for Demographics"() { diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy new file mode 100644 index 000000000..6c866e985 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy @@ -0,0 +1,24 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation + +import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader +import gov.hhs.cdc.trustedintermediary.wrappers.Logger +import spock.lang.Specification + +class TransformationRuleEngineTest extends Specification { + def engine = TransformationRuleEngine.getInstance("transformation_definitions.json") + def mockRuleLoader = Mock(RuleLoader) + def mockLogger = Mock(Logger) + def mockRule = Mock(TransformationRule) + + def setup() { + TestApplicationContext.reset() + TestApplicationContext.init() + TestApplicationContext.register(RuleLoader, mockRuleLoader) + TestApplicationContext.register(Logger, mockLogger) + TestApplicationContext.register(RuleEngine, engine) + + TestApplicationContext.injectRegisteredImplementations() + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy new file mode 100644 index 000000000..6b30a8577 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy @@ -0,0 +1,17 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation + +import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.wrappers.Logger +import spock.lang.Specification + +class TransformationRuleTest extends Specification { + + def mockLogger = Mock(Logger) + + def setup() { + TestApplicationContext.reset() + TestApplicationContext.init() + TestApplicationContext.register(Logger, mockLogger) + TestApplicationContext.injectRegisteredImplementations() + } +} From e201d839f65ab68991a9f1251229085e518b7789 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 15:47:18 -0700 Subject: [PATCH 081/151] Added back demographics transformation rule with fhirpath condition --- .../e2e/DemographicsTest.groovy | 37 +++++++++---------- .../custom/convertDemographicsToOrder.java | 2 - .../resources/transformation_definitions.json | 14 +++++++ 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy b/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy index 7c58f063d..389650e6d 100644 --- a/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy +++ b/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy @@ -29,25 +29,24 @@ class DemographicsTest extends Specification { parsedJsonBody.patientId == expectedPatientId } - // Commenting out this temporarily while figuring out demographics condition to be able to add rule - // def "payload file check"() { - // when: - // def response = demographicsClient.submit(newbornPatientJsonFileString, true) - // def parsedResponseBody = JsonParser.parseContent(response) - // def sentPayload = SentPayloadReader.read() - // def parsedSentPayload = JsonParser.parse(sentPayload) - // - // then: - // response.getCode() == 200 - // parsedSentPayload.entry[0].resource.resourceType == "MessageHeader" - // parsedSentPayload.entry[2].resource.resourceType == "ServiceRequest" - // - // parsedSentPayload.entry[1].resource.resourceType == "Patient" - // parsedSentPayload.entry[1].resource.id == "infant-twin-1" - // - // parsedSentPayload.entry[1].resource.identifier[1].value == parsedResponseBody.patientId //the second (index 1) identifier so happens to be the MRN - // parsedSentPayload.resourceType + "/" + parsedSentPayload.id == parsedResponseBody.fhirResourceId - // } + def "payload file check"() { + when: + def response = demographicsClient.submit(newbornPatientJsonFileString, true) + def parsedResponseBody = JsonParser.parseContent(response) + def sentPayload = SentPayloadReader.read() + def parsedSentPayload = JsonParser.parse(sentPayload) + + then: + response.getCode() == 200 + parsedSentPayload.entry[0].resource.resourceType == "MessageHeader" + parsedSentPayload.entry[2].resource.resourceType == "ServiceRequest" + + parsedSentPayload.entry[1].resource.resourceType == "Patient" + parsedSentPayload.entry[1].resource.id == "infant-twin-1" + + parsedSentPayload.entry[1].resource.identifier[1].value == parsedResponseBody.patientId //the second (index 1) identifier so happens to be the MRN + parsedSentPayload.resourceType + "/" + parsedSentPayload.id == parsedResponseBody.fhirResourceId + } def "return a 400 response when request has unexpected format"() { given: diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java index c39ffe531..43288369d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java @@ -6,8 +6,6 @@ import java.util.Map; import org.hl7.fhir.r4.model.Bundle; -// todo: add this transformation entry in the transformation_definitions file once we have a -// condition to trigger it public class convertDemographicsToOrder implements CustomFhirTransformation { @Override diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 6fbbd07d2..4bcd4a398 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -11,6 +11,20 @@ } ] }, + { + "name": "convertDemographicsToOrder", + "description": "Convert Demographics to Order", + "message": "", + "conditions": [ + "Bundle.entry.resource.is(FHIR.Patient)" + ], + "rules": [ + { + "name": "convertToOrder", + "args": { } + } + ] + }, { "name": "convertToOmlOrder", "description": "Convert ORM to OML Order", From 4130d7243efbd0f0845536ecf8d81c9917ff0d2e Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 16:06:55 -0700 Subject: [PATCH 082/151] Fixed names --- etor/src/main/resources/transformation_definitions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 4bcd4a398..3bdb95e60 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -16,11 +16,11 @@ "description": "Convert Demographics to Order", "message": "", "conditions": [ - "Bundle.entry.resource.is(FHIR.Patient)" + "Bundle.entry.resource.is(Patient)" ], "rules": [ { - "name": "convertToOrder", + "name": "convertDemographicsToOrder", "args": { } } ] From ea8ab6893df92878cecce9349784ebdc94a414a1 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 1 May 2024 16:17:01 -0700 Subject: [PATCH 083/151] Fixed demographics condition and e2e test --- .../resources/transformation_definitions.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 3bdb95e60..94f937553 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -1,26 +1,26 @@ { "definitions": [ { - "name": "addEtorProcessingTag", - "description": "Add ETOR Processing Tag", + "name": "convertDemographicsToOrder", + "description": "Convert Demographics to Order", "message": "", - "conditions": [ ], + "conditions": [ + "Bundle.entry.resource.all($this is Patient)" + ], "rules": [ { - "name": "addEtorProcessingTag", + "name": "convertDemographicsToOrder", "args": { } } ] }, { - "name": "convertDemographicsToOrder", - "description": "Convert Demographics to Order", + "name": "addEtorProcessingTag", + "description": "Add ETOR Processing Tag", "message": "", - "conditions": [ - "Bundle.entry.resource.is(Patient)" - ], + "conditions": [ ], "rules": [ { - "name": "convertDemographicsToOrder", + "name": "addEtorProcessingTag", "args": { } } ] From d2521c1cfda5171f51e657e2ce5b15ac308b3abe Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Thu, 2 May 2024 09:15:10 -0700 Subject: [PATCH 084/151] refactoring: helper method -> applyTransoformation --- .../transformation/TransformationRule.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 2e741b16f..e6d028714 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -36,21 +36,24 @@ public TransformationRule( public void runRule(FhirResource resource) { - for (TransformationRuleMethod transformation : this.getRules()) { - String name = transformation.name(); - Map args = transformation.args(); + this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); + } - try { - Class clazz = loadCustomTransformationClassFromFile(name); - Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); - method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InvocationTargetException - | InstantiationException e) { - logger.logError("Error invoking method: " + name, e); - } + private void applyTransformation( + TransformationRuleMethod transformation, FhirResource resource) { + String name = transformation.name(); + Map args = transformation.args(); + + try { + Class clazz = loadCustomTransformationClassFromFile(name); + Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); + method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InvocationTargetException + | InstantiationException e) { + logger.logError("Error invoking method: " + name, e); } } From 2bf76d717dead7d7e7a28dc7b027bd5bfad99618 Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Thu, 2 May 2024 09:46:57 -0700 Subject: [PATCH 085/151] refactor: remove assert and add custom exception handling --- .../etor/ruleengine/RuleLoaderException.java | 4 ++++ .../TransformationRuleEngine.java | 21 ++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderException.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderException.java index 449f46e97..290ced3e5 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderException.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderException.java @@ -6,4 +6,8 @@ public class RuleLoaderException extends Exception { public RuleLoaderException(String message, Throwable cause) { super(message, cause); } + + public RuleLoaderException(String message) { + super(message); + } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index ad82afa45..09c74dbc6 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -6,6 +6,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -38,14 +39,18 @@ public void ensureRulesLoaded() throws RuleLoaderException { if (rules.isEmpty()) { synchronized (this) { if (rules.isEmpty()) { - InputStream resourceStream = - getClass() - .getClassLoader() - .getResourceAsStream(ruleDefinitionsFileName); - assert resourceStream != null; - List parsedRules = - ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); - this.rules.addAll(parsedRules); + String path = ruleDefinitionsFileName; + try (InputStream resourceStream = + getClass().getClassLoader().getResourceAsStream(path)) { + if (resourceStream == null) { + throw new RuleLoaderException("No resource found at " + path); + } + List parsedRules = + ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); + this.rules.addAll(parsedRules); + } catch (IOException e) { + throw new RuleLoaderException("Failed to load rules from " + path, e); + } } } } From e8634691c84ba531cc26f9a6b44138bee0ac131b Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 09:50:42 -0700 Subject: [PATCH 086/151] Added helper static methods to get root path for the project --- .../transformation/TransformationRule.java | 8 +++--- .../trustedintermediary/ExamplesHelper.groovy | 27 +++++++++++++++++++ ...sformationRuleEngineIntegrationTest.groovy | 13 +++++++++ .../context/ApplicationContext.java | 10 +++++++ 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index e6d028714..6e2a5879d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -1,12 +1,12 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; +import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Map; @@ -61,10 +61,10 @@ private static Class loadCustomTransformationClassFromFile(String className) throws ClassNotFoundException { String customPackageName = "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; + Path rootPath = ApplicationContext.getRootPath(); Path customTransformationPath = - Paths.get( - System.getProperty("user.dir"), - "../etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/"); + rootPath.resolve( + "etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/"); File[] customTransformationFiles = customTransformationPath.toFile().listFiles(); assert customTransformationFiles != null; diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy new file mode 100644 index 000000000..c8deff7b3 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy @@ -0,0 +1,27 @@ +package gov.hhs.cdc.trustedintermediary + +import gov.hhs.cdc.trustedintermediary.context.ApplicationContext +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource +import org.hl7.fhir.r4.model.Bundle + +import java.nio.file.Files +import java.nio.file.Path + +class ExamplesHelper { + + static List getExampleFhirResources(String messageType = "") { + def testExampleFilesPath = ApplicationContext.getExamplesPath().resolve("Test") + return Files.walk(testExampleFilesPath.resolve(messageType)) + .filter { it.toString().endsWith(".fhir") } + .map { new HapiFhirResource(HapiFhirImplementation.getInstance().parseResource(Files.readString(it), Bundle)) } + .collect() + } + + static HapiFhirResource getExampleFhirResource(String relativeFilePath) { + def testExampleFilesPath = ApplicationContext.getExamplesPath().resolve("Test") + def resourceFilePath = testExampleFilesPath.resolve(relativeFilePath) + def parsedResource = HapiFhirImplementation.getInstance().parseResource(Files.readString(resourceFilePath), Bundle) + return new HapiFhirResource(parsedResource) + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index d5cbc35c6..7a5398a16 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation +import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation @@ -43,6 +44,18 @@ class TransformationRuleEngineIntegrationTest extends Specification { } def "transformation rules filter and run rules for ORM messages"() { + given: + def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + def rule = createValidationRule([], [validation]) + 0 * mockLogger.logWarning(_ as String) + 0 * mockLogger.logError(_ as String, _ as Exception) + + expect: + rule.runRule(fhirResource) + + where: + testFile | validation + "e2e/orders/001_OML_O21_short.fhir" | "Bundle.entry.resource.ofType(MessageHeader).focus.resolve().category.exists()" } def "transformation rules filter and run rules for OML messages"() { diff --git a/shared/src/main/java/gov/hhs/cdc/trustedintermediary/context/ApplicationContext.java b/shared/src/main/java/gov/hhs/cdc/trustedintermediary/context/ApplicationContext.java index 86a73a923..3f4504c9f 100644 --- a/shared/src/main/java/gov/hhs/cdc/trustedintermediary/context/ApplicationContext.java +++ b/shared/src/main/java/gov/hhs/cdc/trustedintermediary/context/ApplicationContext.java @@ -5,6 +5,8 @@ package gov.hhs.cdc.trustedintermediary.context; import java.lang.reflect.Field; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -164,4 +166,12 @@ public static String getProperty(String key, String defaultValue) { public static String getEnvironment() { return getProperty("ENV", "local"); } + + public static Path getRootPath() { + return Paths.get(System.getProperty("user.dir")).getParent(); + } + + public static Path getExamplesPath() { + return getRootPath().resolve("examples"); + } } From f0c2b7749d4a3f68835d1b7d3706e13e46307747 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 09:51:08 -0700 Subject: [PATCH 087/151] Missed in last commit --- ...ValidationRuleEngineIntegrationTest.groovy | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy index 0cc2d78f1..2a96079d6 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation +import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation @@ -15,11 +16,7 @@ import org.hl7.fhir.r4.model.Organization import org.hl7.fhir.r4.model.Reference import spock.lang.Specification -import java.nio.file.Files -import java.nio.file.Path - class ValidationRuleEngineIntegrationTest extends Specification { - def testExampleFilesPath = "../examples/Test" def fhir = HapiFhirImplementation.getInstance() def engine = ValidationRuleEngine.getInstance("validation_definitions.json") def mockLogger = Mock(Logger) @@ -50,7 +47,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { def "validation doesn't break for any of the sample test messages"() { given: - def exampleFhirResources = getExampleFhirResources("Orders") + def exampleFhirResources = ExamplesHelper.getExampleFhirResources("Orders") when: exampleFhirResources.each { resource -> @@ -63,7 +60,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { def "validation rule with resolve() works as expected"() { given: - def fhirResource = getExampleFhirResource("e2e/orders/001_OML_O21_short.fhir") + def fhirResource = ExamplesHelper.getExampleFhirResource("e2e/orders/001_OML_O21_short.fhir") def validation = "Bundle.entry.resource.ofType(MessageHeader).focus.resolve().category.exists()" def rule = createValidationRule([], [validation]) @@ -77,7 +74,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { def "validation rules pass for test files"() { given: - def fhirResource = getExampleFhirResource(testFile) + def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) def rule = createValidationRule([], [validation]) 0 * mockLogger.logWarning(_ as String) 0 * mockLogger.logError(_ as String, _ as Exception) @@ -87,7 +84,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { where: testFile | validation - "e2e/orders/001_OML_O21_short.fhir" | "Bundle.entry.resource.ofType(MessageHeader).focus.resolve().category.exists()" + "e2e/orders/001_OML_O21_short.fhir" | "Bundle.entry.resource.ofType(MessageHeader).focus.resolve().category.exists()" "Orders/003_AL_ORM_O01_NBS_Fully_Populated_1_hl7_translation.fhir" | "Bundle.entry.resource.ofType(MessageHeader).destination.receiver.resolve().identifier.where(system = 'urn:ietf:rfc:3986').value.exists()" // Once we fix the mapping for ORM from story #900 and update the FHIR files in /examples/Test/Orders, we can uncomment the below line // "Orders/003_AL_ORM_O01_NBS_Fully_Populated_1_hl7_translation.fhir" | "Bundle.entry.resource.ofType(Observation).where(code.coding.code = '57723-9').value.coding.code.exists()" @@ -95,7 +92,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { def "validation rules fail for test files"() { given: - def fhirResource = getExampleFhirResource(testFile) + def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) def rule = createValidationRule([], [validation]) 1 * mockLogger.logWarning(_ as String) 0 * mockLogger.logError(_ as String, _ as Exception) @@ -192,15 +189,4 @@ class ValidationRuleEngineIntegrationTest extends Specification { ruleValidations, ) } - - List getExampleFhirResources(String messageType = "") { - return Files.walk(Path.of(testExampleFilesPath, messageType)) - .filter { it.toString().endsWith(".fhir") } - .map { new HapiFhirResource(fhir.parseResource(Files.readString(it), Bundle)) } - .collect() - } - - HapiFhirResource getExampleFhirResource(String relativeFilePath) { - return new HapiFhirResource(fhir.parseResource(Files.readString(Path.of(testExampleFilesPath, relativeFilePath)), Bundle)) - } } From 4ddce468607dd61c5a58162224eeb6e531b72dca Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 09:55:57 -0700 Subject: [PATCH 088/151] Extracted helper method to create a fhir bundle --- .../FhirBundleHelper.groovy | 33 +++++++++++++++++++ ...ValidationRuleEngineIntegrationTest.groovy | 33 +++---------------- 2 files changed, 37 insertions(+), 29 deletions(-) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy new file mode 100644 index 000000000..a4bdededd --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy @@ -0,0 +1,33 @@ +package gov.hhs.cdc.trustedintermediary + +import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.MessageHeader +import org.hl7.fhir.r4.model.Organization +import org.hl7.fhir.r4.model.Reference + +class FhirBundleHelper { + + static Bundle createMessageBundle(Map params) { + String messageTypeCode = params.messageTypeCode as String ?: "ORM_O01" + Organization receiverOrganization = params.receiverOrganization as Organization ?: new Organization() + MessageHeader messageHeader = params.messageType as MessageHeader ?: new MessageHeader() + + MessageHeader.MessageDestinationComponent destination = messageHeader.addDestination() + String receiverOrganizationFullUrl = "Organization/" + receiverOrganization.getId() + destination.setReceiver(new Reference(receiverOrganizationFullUrl)) + + Coding eventCoding = new Coding() + eventCoding.setSystem("http://terminology.hl7.org/CodeSystem/v2-0003") + String[] parts = messageTypeCode.split("_") + eventCoding.setCode(parts[1]) + eventCoding.setDisplay(String.format("%s^%s^%s", parts[0], parts[1], messageTypeCode)) + messageHeader.setEvent(eventCoding) + + Bundle bundle = new Bundle() + bundle.setType(Bundle.BundleType.MESSAGE) + bundle.addEntry().setResource(messageHeader) + bundle.addEntry().setFullUrl(receiverOrganizationFullUrl).setResource(receiverOrganization) + return bundle + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy index 2a96079d6..fd1984f9e 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineIntegrationTest.groovy @@ -1,6 +1,7 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation import gov.hhs.cdc.trustedintermediary.ExamplesHelper +import gov.hhs.cdc.trustedintermediary.FhirBundleHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation @@ -10,10 +11,7 @@ import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import org.hl7.fhir.r4.model.Bundle -import org.hl7.fhir.r4.model.Coding -import org.hl7.fhir.r4.model.MessageHeader import org.hl7.fhir.r4.model.Organization -import org.hl7.fhir.r4.model.Reference import spock.lang.Specification class ValidationRuleEngineIntegrationTest extends Specification { @@ -118,7 +116,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { .addIdentifier() .setSystem("urn:ietf:rfc:3986") .setValue("simulated-hospital-id") - def bundle = createMessageBundle(receiverOrganization: receiverOrganization) + def bundle = FhirBundleHelper.createMessageBundle(receiverOrganization: receiverOrganization) // for some reason, we need to encode and decode the bundle for resolve() to work def fhirResource = new HapiFhirResource(fhir.parseResource(fhir.encodeResourceToJson(bundle), Bundle)) rule.runRule(fhirResource) @@ -134,7 +132,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { .addIdentifier() .setSystem("another-system") .setValue("simulated-hospital-id") - bundle = createMessageBundle(receiverOrganization: receiverOrganization) + bundle = FhirBundleHelper.createMessageBundle(receiverOrganization: receiverOrganization) fhirResource = new HapiFhirResource(fhir.parseResource(fhir.encodeResourceToJson(bundle), Bundle)) rule.runRule(fhirResource) @@ -148,7 +146,7 @@ class ValidationRuleEngineIntegrationTest extends Specification { receiverOrganization .addIdentifier() .setValue("simulated-hospital-id") - bundle = createMessageBundle(receiverOrganization: receiverOrganization) + bundle = FhirBundleHelper.createMessageBundle(receiverOrganization: receiverOrganization) fhirResource = new HapiFhirResource(fhir.parseResource(fhir.encodeResourceToJson(bundle), Bundle)) rule.runRule(fhirResource) @@ -157,29 +155,6 @@ class ValidationRuleEngineIntegrationTest extends Specification { 0 * mockLogger.logError(_ as String, _ as Exception) } - Bundle createMessageBundle(Map params) { - String messageTypeCode = params.messageTypeCode as String ?: "ORM_O01" - Organization receiverOrganization = params.receiverOrganization as Organization ?: new Organization() - MessageHeader messageHeader = params.messageType as MessageHeader ?: new MessageHeader() - - MessageHeader.MessageDestinationComponent destination = messageHeader.addDestination() - String receiverOrganizationFullUrl = "Organization/" + receiverOrganization.getId() - destination.setReceiver(new Reference(receiverOrganizationFullUrl)) - - Coding eventCoding = new Coding() - eventCoding.setSystem("http://terminology.hl7.org/CodeSystem/v2-0003") - String[] parts = messageTypeCode.split("_") - eventCoding.setCode(parts[1]) - eventCoding.setDisplay(String.format("%s^%s^%s", parts[0], parts[1], messageTypeCode)) - messageHeader.setEvent(eventCoding) - - Bundle bundle = new Bundle() - bundle.setType(Bundle.BundleType.MESSAGE) - bundle.addEntry().setResource(messageHeader) - bundle.addEntry().setFullUrl(receiverOrganizationFullUrl).setResource(receiverOrganization) - return bundle - } - ValidationRule createValidationRule(List ruleConditions, List ruleValidations) { return new ValidationRule( "Rule name", From f536a69f52464defc49df014a20a296bc0582cdb Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Thu, 2 May 2024 09:55:33 -0700 Subject: [PATCH 089/151] Refactoring: runRules continues to run rules if one fails --- .../transformation/TransformationRuleEngine.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 09c74dbc6..aa8c38bcf 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -64,6 +64,19 @@ public void runRules(FhirResource resource) { logger.logError("Failed to load rules definitions", e); return; } + + rules.forEach( + rule -> { + try { + if (rule.shouldRun(resource)) { + rule.runRule((resource)); + } + } catch (Exception e) { // Do we need a custom exception for rules? + logger.logError( + "Error executing rule: " + rule.getClass().getSimpleName(), e); + } + }); + for (TransformationRule rule : rules) { if (rule.shouldRun(resource)) { rule.runRule(resource); From b1ea6fc6daa9dd968032971fe0b87488b3b7e39d Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Thu, 2 May 2024 11:58:44 -0500 Subject: [PATCH 090/151] Update TransformationRuleTest with basic getter/setter test --- .../TransformationRuleTest.groovy | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy index 6b30a8577..cb1622dff 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy @@ -1,17 +1,44 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.Logger import spock.lang.Specification class TransformationRuleTest extends Specification { def mockLogger = Mock(Logger) - def setup() { TestApplicationContext.reset() TestApplicationContext.init() TestApplicationContext.register(Logger, mockLogger) TestApplicationContext.injectRegisteredImplementations() } + + def "TransformationRule gets and sets correctly"() { + given: + def ruleName = "Rule name" + def ruleDescription = "Rule Description" + def ruleMessage = "Rule Warning Message" + def ruleConditions = ["condition1", "condition2"] + def ruleActions = [ + new TransformationRuleMethod("acton1", null), + new TransformationRuleMethod("action2", null) + ] + TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) + + when: + def rule = new TransformationRule(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions) + + + then: + rule.getName() == ruleName + rule.getDescription() == ruleDescription + rule.getMessage() == ruleMessage + rule.getConditions() == ruleConditions + rule.getRules() == ruleActions + } + + def "runRule() works correctly"() { + } } From 1c9eb48a9614256d3df35bf1c733c8d6556d8987 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 2 May 2024 13:08:53 -0400 Subject: [PATCH 091/151] Update TransformationRuleEngineTest.groovy Add coverage for TransformationRuleEngine --- .../TransformationRuleEngineTest.groovy | 119 +++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy index 6c866e985..71098a381 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy @@ -1,24 +1,139 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException import gov.hhs.cdc.trustedintermediary.wrappers.Logger +import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference import spock.lang.Specification class TransformationRuleEngineTest extends Specification { - def engine = TransformationRuleEngine.getInstance("transformation_definitions.json") + def ruleEngine = TransformationRuleEngine.getInstance("transformation_definitions.json") def mockRuleLoader = Mock(RuleLoader) def mockLogger = Mock(Logger) def mockRule = Mock(TransformationRule) def setup() { + ruleEngine.unloadRules() + TestApplicationContext.reset() TestApplicationContext.init() TestApplicationContext.register(RuleLoader, mockRuleLoader) TestApplicationContext.register(Logger, mockLogger) - TestApplicationContext.register(RuleEngine, engine) + TestApplicationContext.register(RuleEngine, ruleEngine) TestApplicationContext.injectRegisteredImplementations() } + + def "ensureRulesLoaded happy path"() { + given: + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] + + when: + ruleEngine.ensureRulesLoaded() + + then: + 1 * mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] + ruleEngine.rules.size() == 1 + } + + def "ensureRulesLoaded loads rules only once by default"() { + given: + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] + + when: + ruleEngine.ensureRulesLoaded() + ruleEngine.ensureRulesLoaded() // Call twice to test if rules are loaded only once + + then: + 1 * mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] + ruleEngine.rules.size() == 1 + } + + def "ensureRulesLoaded loads rules only once on multiple threads"() { + given: + def threadsNum = 10 + def iterations = 4 + + when: + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [mockRule] + List threads = [] + (1..threadsNum).each { threadId -> + threads.add(new Thread({ + for (int i = 0; i < iterations; i++) { + ruleEngine.ensureRulesLoaded() + } + })) + } + threads*.start() + threads*.join() + + then: + 1 * mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) + } + + def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { + given: + def exception = new RuleLoaderException("Error loading rules", new Exception()) + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> { + mockLogger.logError("Error loading rules", exception) + return [] + } + + when: + ruleEngine.runRules(Mock(FhirResource)) + + then: + 1 * mockLogger.logError(_ as String, exception) + } + + def "runRules handles logging warning correctly"() { + given: + def failedTransformationMessage = "Failed transformation message" + def fullFailedTransformationMessage = "Transformation failed: " + failedTransformationMessage + def fhirBundle = Mock(FhirResource) + def invalidRule = Mock(TransformationRule) + invalidRule.getMessage() >> failedTransformationMessage + invalidRule.shouldRun(fhirBundle) >> true + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [invalidRule] + + when: + invalidRule.runRule(fhirBundle) >> { + mockLogger.logWarning(fullFailedTransformationMessage) + } + ruleEngine.runRules(fhirBundle) + + then: + 1 * mockLogger.logWarning(fullFailedTransformationMessage) + + when: + invalidRule.runRule(fhirBundle) >> null + invalidRule.shouldRun(fhirBundle) >> true + ruleEngine.runRules(fhirBundle) + + then: + 0 * mockLogger.logWarning(fullFailedTransformationMessage) + + when: + invalidRule.shouldRun(fhirBundle) >> false + ruleEngine.runRules(fhirBundle) + + then: + 0 * mockLogger.logWarning(fullFailedTransformationMessage) + } + + + def "runRules logs an error and doesn't run any rules when there's a RuleLoaderException"() { + given: + def exception = new RuleLoaderException("Error loading rules", new Exception()) + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> { throw exception } + + when: + ruleEngine.runRules(Mock(FhirResource)) + + then: + 1 * mockLogger.logError(_ as String, exception) + } } From 20102deb7df15342fc903ea0c23f800dd873322c Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 10:45:07 -0700 Subject: [PATCH 092/151] WIP: fixing build for others --- ...sformationRuleEngineIntegrationTest.groovy | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 7a5398a16..46f099df9 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -3,6 +3,7 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson @@ -41,22 +42,34 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logError(_ as String, _ as Exception) + (1.._) * mockLogger.logInfo(_ as String, _ as String) } - def "transformation rules filter and run rules for ORM messages"() { - given: - def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - def rule = createValidationRule([], [validation]) - 0 * mockLogger.logWarning(_ as String) - 0 * mockLogger.logError(_ as String, _ as Exception) - - expect: - rule.runRule(fhirResource) - - where: - testFile | validation - "e2e/orders/001_OML_O21_short.fhir" | "Bundle.entry.resource.ofType(MessageHeader).focus.resolve().category.exists()" - } + // def "transformation rules filter and run rules for ORM messages"() { + // given: + // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + // def rule = createTransformationRuleFromName(transformationMethodName) + // 0 * mockLogger.logError(_ as String, _ as Exception) + // + // when: + // def testFile = "e2e/orders/001_OML_O21_short.fhir" + // def transformationMethodName = "addContactSectionToPatientResource" + // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + // def transformedFhirResource = addContactSectionToPatientResource(fhirResource) + // + // then: + // def rule = createTransformationRuleFromName(transformationMethodName) + // rule.runRule(fhirResource) + // + // then: + // + //// expect: + //// rule.runRule(fhirResource) + //// + //// where: + //// testFile | transformationMethodName + //// "e2e/orders/001_OML_O21_short.fhir" | "addContactSectionToPatientResource" + // } def "transformation rules filter and run rules for OML messages"() { } @@ -66,4 +79,26 @@ class TransformationRuleEngineIntegrationTest extends Specification { def "transformation rules filter and run rules for Demographics"() { } + + TransformationRule createTransformationRule(List conditions, List transformations) { + return new TransformationRule( + "Rule name", + "Rule description", + "Rule message", + conditions, + transformations, + ) + } + + TransformationRule createTransformationRuleFromName(String transformationName) { + return new TransformationRule( + "Rule name", + "Rule description", + "Rule message", + [], + [ + new TransformationRuleMethod(transformationName, new HashMap()) + ] + ) + } } From 9d8b27c94e1b4e5fea1615a99ae77cfc86a1f11d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 10:50:29 -0700 Subject: [PATCH 093/151] Added info logging when applying rule --- .../etor/ruleengine/transformation/TransformationRule.java | 1 + .../groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 6e2a5879d..e5150017f 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -43,6 +43,7 @@ private void applyTransformation( TransformationRuleMethod transformation, FhirResource resource) { String name = transformation.name(); Map args = transformation.args(); + logger.logInfo("Applying transformation: ", name); try { Class clazz = loadCustomTransformationClassFromFile(name); diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy index c8deff7b3..5c00a7c68 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ExamplesHelper.groovy @@ -6,7 +6,6 @@ import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource import org.hl7.fhir.r4.model.Bundle import java.nio.file.Files -import java.nio.file.Path class ExamplesHelper { From 479f0ccfbaa11a975069b2b1025e3b388877abb1 Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Thu, 2 May 2024 10:31:41 -0700 Subject: [PATCH 094/151] refactoring: create a classCache and separate responsibilities in loadCustomTransformationClassFromFile() --- .../transformation/TransformationRule.java | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index e5150017f..7c0a5e490 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -1,12 +1,10 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; -import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; -import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.file.Path; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,6 +17,8 @@ */ public class TransformationRule extends Rule { + private static final Map> classCache = new HashMap<>(); + /** * Do not delete this constructor! It is used for JSON deserialization when loading rules from a * file. @@ -60,24 +60,18 @@ private void applyTransformation( private static Class loadCustomTransformationClassFromFile(String className) throws ClassNotFoundException { - String customPackageName = - "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; - Path rootPath = ApplicationContext.getRootPath(); - Path customTransformationPath = - rootPath.resolve( - "etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/"); - File[] customTransformationFiles = customTransformationPath.toFile().listFiles(); - assert customTransformationFiles != null; - - for (File file : customTransformationFiles) { - String fileName = file.getName().replace(".java", ""); - if (file.isFile() - && (file.getName().endsWith(".java")) - && (fileName.equalsIgnoreCase(className))) { - return Class.forName(customPackageName + "." + className); - } + if (classCache.containsKey(className)) { + return classCache.get(className); } - throw new ClassNotFoundException("No custom transformation file found for " + className); + Class clazz = Class.forName(getFullClassName(className)); + classCache.put(className, clazz); + return clazz; + } + + private static String getFullClassName(String className) throws ClassNotFoundException { + String packageName = + "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; + return packageName + "." + className; } } From cddb40f6b3e46b5791a4f337b1755d39fefc0684 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 2 May 2024 13:55:56 -0400 Subject: [PATCH 095/151] Update TransformationRuleEngineTest.groovy --- .../TransformationRuleEngineTest.groovy | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy index 71098a381..791f76eee 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy @@ -91,37 +91,36 @@ class TransformationRuleEngineTest extends Specification { def "runRules handles logging warning correctly"() { given: - def failedTransformationMessage = "Failed transformation message" - def fullFailedTransformationMessage = "Transformation failed: " + failedTransformationMessage + def applyingTransformationMessage = "Applying transformation" def fhirBundle = Mock(FhirResource) - def invalidRule = Mock(TransformationRule) - invalidRule.getMessage() >> failedTransformationMessage - invalidRule.shouldRun(fhirBundle) >> true - mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [invalidRule] + def testRule = Mock(TransformationRule) + testRule.getMessage() >> applyingTransformationMessage + testRule.shouldRun(fhirBundle) >> true + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [testRule] when: - invalidRule.runRule(fhirBundle) >> { - mockLogger.logWarning(fullFailedTransformationMessage) + testRule.runRule(fhirBundle) >> { + mockLogger.logInfo(applyingTransformationMessage) } ruleEngine.runRules(fhirBundle) then: - 1 * mockLogger.logWarning(fullFailedTransformationMessage) + 1 * mockLogger.logInfo(applyingTransformationMessage) when: - invalidRule.runRule(fhirBundle) >> null - invalidRule.shouldRun(fhirBundle) >> true + testRule.runRule(fhirBundle) >> null + testRule.shouldRun(fhirBundle) >> true ruleEngine.runRules(fhirBundle) then: - 0 * mockLogger.logWarning(fullFailedTransformationMessage) + 0 * mockLogger.logInfo(applyingTransformationMessage) when: - invalidRule.shouldRun(fhirBundle) >> false + testRule.shouldRun(fhirBundle) >> false ruleEngine.runRules(fhirBundle) then: - 0 * mockLogger.logWarning(fullFailedTransformationMessage) + 0 * mockLogger.logInfo(applyingTransformationMessage) } From 8c8f750dbbd385f5cb416dd9fb8320fa81cd42cb Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 2 May 2024 14:02:30 -0400 Subject: [PATCH 096/151] Remove a duplicate for loop --- .../transformation/TransformationRule.java | 8 +------- .../transformation/TransformationRuleEngine.java | 15 ++------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 7c0a5e490..f8fbc1431 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -2,7 +2,6 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; @@ -35,7 +34,6 @@ public TransformationRule( } public void runRule(FhirResource resource) { - this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); } @@ -49,11 +47,7 @@ private void applyTransformation( Class clazz = loadCustomTransformationClassFromFile(name); Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InvocationTargetException - | InstantiationException e) { + } catch (Exception e) { logger.logError("Error invoking method: " + name, e); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index aa8c38bcf..d29b40b6d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -67,20 +67,9 @@ public void runRules(FhirResource resource) { rules.forEach( rule -> { - try { - if (rule.shouldRun(resource)) { - rule.runRule((resource)); - } - } catch (Exception e) { // Do we need a custom exception for rules? - logger.logError( - "Error executing rule: " + rule.getClass().getSimpleName(), e); + if (rule.shouldRun(resource)) { + rule.runRule((resource)); } }); - - for (TransformationRule rule : rules) { - if (rule.shouldRun(resource)) { - rule.runRule(resource); - } - } } } From e64d77513ce8352e6bd0c0d58f9ee4554a0918ea Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Thu, 2 May 2024 11:19:24 -0700 Subject: [PATCH 097/151] refactoring: detail exception handling for TransformationRule::applyTransformation --- .../ruleengine/transformation/TransformationRule.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index f8fbc1431..705973443 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -2,6 +2,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; @@ -47,8 +48,13 @@ private void applyTransformation( Class clazz = loadCustomTransformationClassFromFile(name); Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); - } catch (Exception e) { - logger.logError("Error invoking method: " + name, e); + } catch (NoSuchMethodException + | IllegalAccessException + | InvocationTargetException + | InstantiationException e) { + logger.logError("Error invoking method: " + name + ", due to: " + e.getMessage(), e); + } catch (ClassNotFoundException e) { + logger.logError("Transformation class not found: " + name, e); } } From c9614e7c3aa010c668d3a07e7d1ca696f0518bae Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 11:29:13 -0700 Subject: [PATCH 098/151] Added integration test + cleanup and refactoring --- .../transformation/TransformationRule.java | 17 ++++++++++++----- ...ansformationRuleEngineIntegrationTest.groovy | 12 ++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 705973443..aee3d9b68 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -46,8 +46,7 @@ private void applyTransformation( try { Class clazz = loadCustomTransformationClassFromFile(name); - Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); - method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); + executeCustomTransformationMethod(clazz, resource, args); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException @@ -58,18 +57,26 @@ private void applyTransformation( } } - private static Class loadCustomTransformationClassFromFile(String className) + static Class loadCustomTransformationClassFromFile(String className) throws ClassNotFoundException { if (classCache.containsKey(className)) { return classCache.get(className); } - Class clazz = Class.forName(getFullClassName(className)); + Class clazz = Class.forName(getCustomTransformationFullClassName(className)); classCache.put(className, clazz); return clazz; } - private static String getFullClassName(String className) throws ClassNotFoundException { + static void executeCustomTransformationMethod( + Class clazz, FhirResource resource, Map args) + throws NoSuchMethodException, InvocationTargetException, InstantiationException, + IllegalAccessException { + Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); + method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); + } + + private static String getCustomTransformationFullClassName(String className) { String packageName = "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; return packageName + "." + className; diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 46f099df9..f0cb6c9ee 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -31,6 +31,8 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.injectRegisteredImplementations() + + engine.ensureRulesLoaded() } def "transformation rules run without error"() { @@ -45,6 +47,16 @@ class TransformationRuleEngineIntegrationTest extends Specification { (1.._) * mockLogger.logInfo(_ as String, _ as String) } + def "all transformations in the definitions file have existing custom methods"() { + when: + def transformationMethodNames = engine.rules.collect { rule -> rule.name } + + then: + transformationMethodNames.each { transformationMethodName -> + assert TransformationRule.loadCustomTransformationClassFromFile(transformationMethodName) != null + } + } + // def "transformation rules filter and run rules for ORM messages"() { // given: // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) From 043d8638a4d03a35cc49f5a77f08304747425158 Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Thu, 2 May 2024 12:05:20 -0700 Subject: [PATCH 099/151] refactoring: loadClassFromCache and added helper function --- .../transformation/TransformationRule.java | 30 ++++++++++--------- ...sformationRuleEngineIntegrationTest.groovy | 5 ++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index aee3d9b68..7bd539332 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -45,7 +45,7 @@ private void applyTransformation( logger.logInfo("Applying transformation: ", name); try { - Class clazz = loadCustomTransformationClassFromFile(name); + Class clazz = loadClassFromCache(name); executeCustomTransformationMethod(clazz, resource, args); } catch (NoSuchMethodException | IllegalAccessException @@ -57,15 +57,23 @@ private void applyTransformation( } } - static Class loadCustomTransformationClassFromFile(String className) - throws ClassNotFoundException { - if (classCache.containsKey(className)) { - return classCache.get(className); + private static Class loadClassFromCache(String className) throws ClassNotFoundException { + return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); + } + + private static Class loadClassByName(String className) { + String fullClassName = getFullClassName(className); + try { + return Class.forName(fullClassName); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); } + } - Class clazz = Class.forName(getCustomTransformationFullClassName(className)); - classCache.put(className, clazz); - return clazz; + private static String getFullClassName(String className) { + String packageName = + "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; + return packageName + "." + className; } static void executeCustomTransformationMethod( @@ -75,10 +83,4 @@ static void executeCustomTransformationMethod( Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); } - - private static String getCustomTransformationFullClassName(String className) { - String packageName = - "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; - return packageName + "." + className; - } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index f0cb6c9ee..489996a51 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,9 +1,8 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation -import gov.hhs.cdc.trustedintermediary.ExamplesHelper + import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.validation.ValidationRule import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson @@ -53,7 +52,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: transformationMethodNames.each { transformationMethodName -> - assert TransformationRule.loadCustomTransformationClassFromFile(transformationMethodName) != null + assert TransformationRule.loadClassFromCache(transformationMethodName) != null } } From 89a01d0af167d5f3e7f88d61c68e4d1a9c6d3e74 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 2 May 2024 15:19:42 -0400 Subject: [PATCH 100/151] Update TransformationRuleEngineIntegrationTest.groovy add test for etor processing tag --- ...sformationRuleEngineIntegrationTest.groovy | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index f0cb6c9ee..632b069c6 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -57,6 +57,38 @@ class TransformationRuleEngineIntegrationTest extends Specification { } } + // def "Testing accuracy of rule: convertDemographicsToOrder"() { + // given: + // def bundle = new Bundle() + // engine.ensureRulesLoaded() + // engine.rules.removeAll(engine.rules.findAll { + // it.name != "convertDemographicsToOrder" + // }) + // + // when: + // engine.runRules(new HapiFhirResource(bundle)) + // + // then: + // bundle != null + // } + + def "Testing accuracy of rule: addEtorProcessingTag"() { + given: + def untouchedBundle = new Bundle() + def bundle = new Bundle() + engine.ensureRulesLoaded() + engine.rules.removeAll(engine.rules.findAll { + it.name != "addEtorProcessingTag" + }) + + when: + engine.runRules(new HapiFhirResource(bundle)) + + then: + untouchedBundle.entry.isEmpty() + bundle.entry.first().getResource().meta.tag.first().code == "ETOR" + } + // def "transformation rules filter and run rules for ORM messages"() { // given: // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) From 7947e86d170b3cc9adca177fc847df922e3f1516 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 12:24:48 -0700 Subject: [PATCH 101/151] Added integration test to match each rule to a test file and run it --- .../transformation/TransformationRule.java | 24 +++--- ...sformationRuleEngineIntegrationTest.groovy | 77 ++++++------------- 2 files changed, 35 insertions(+), 66 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 7bd539332..3aaaa0cc9 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -38,6 +38,18 @@ public void runRule(FhirResource resource) { this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); } + static Class loadClassFromCache(String className) throws ClassNotFoundException { + return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); + } + + static void executeCustomTransformationMethod( + Class clazz, FhirResource resource, Map args) + throws NoSuchMethodException, InvocationTargetException, InstantiationException, + IllegalAccessException { + Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); + method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); + } + private void applyTransformation( TransformationRuleMethod transformation, FhirResource resource) { String name = transformation.name(); @@ -57,10 +69,6 @@ private void applyTransformation( } } - private static Class loadClassFromCache(String className) throws ClassNotFoundException { - return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); - } - private static Class loadClassByName(String className) { String fullClassName = getFullClassName(className); try { @@ -75,12 +83,4 @@ private static String getFullClassName(String className) { "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; return packageName + "." + className; } - - static void executeCustomTransformationMethod( - Class clazz, FhirResource resource, Map args) - throws NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { - Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); - method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); - } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 489996a51..8b739c898 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,6 +1,7 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation +import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation @@ -48,7 +49,11 @@ class TransformationRuleEngineIntegrationTest extends Specification { def "all transformations in the definitions file have existing custom methods"() { when: - def transformationMethodNames = engine.rules.collect { rule -> rule.name } + def transformationMethodNames = engine.rules.collectMany { + it.rules.collect { + it.name() + } + } then: transformationMethodNames.each { transformationMethodName -> @@ -56,60 +61,24 @@ class TransformationRuleEngineIntegrationTest extends Specification { } } - // def "transformation rules filter and run rules for ORM messages"() { - // given: - // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - // def rule = createTransformationRuleFromName(transformationMethodName) - // 0 * mockLogger.logError(_ as String, _ as Exception) - // - // when: - // def testFile = "e2e/orders/001_OML_O21_short.fhir" - // def transformationMethodName = "addContactSectionToPatientResource" - // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - // def transformedFhirResource = addContactSectionToPatientResource(fhirResource) - // - // then: - // def rule = createTransformationRuleFromName(transformationMethodName) - // rule.runRule(fhirResource) - // - // then: - // - //// expect: - //// rule.runRule(fhirResource) - //// - //// where: - //// testFile | transformationMethodName - //// "e2e/orders/001_OML_O21_short.fhir" | "addContactSectionToPatientResource" - // } - - def "transformation rules filter and run rules for OML messages"() { - } - - def "transformation rules filter and run rules for ORU messages"() { - } - - def "transformation rules filter and run rules for Demographics"() { - } - - TransformationRule createTransformationRule(List conditions, List transformations) { - return new TransformationRule( - "Rule name", - "Rule description", - "Rule message", - conditions, - transformations, + def "transformation rules run for specific test files and all rules have corresponding test files"() { + given: + Map transformationSampleMap = Map.of( + "addEtorProcessingTag", "e2e/orders/001_OML_O21_short.fhir", + "convertDemographicsToOrder", "e2e/demographics/001_Patient_NBS.fhir", + "convertToOmlOrder", "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir", + "addContactSectionToPatientResource", "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir", ) - } - TransformationRule createTransformationRuleFromName(String transformationName) { - return new TransformationRule( - "Rule name", - "Rule description", - "Rule message", - [], - [ - new TransformationRuleMethod(transformationName, new HashMap()) - ] - ) + engine.rules.each { rule -> + when: + def testFile = transformationSampleMap.get(rule.name) + def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + rule.runRule(fhirResource) + + then: + 0 * mockLogger.logError(_ as String, _ as Exception) + 1 * mockLogger.logInfo(_ as String, _ as String) + } } } From e461d562bcf575c6d1cc385d5cd2bf4122fa10ae Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 2 May 2024 15:30:37 -0400 Subject: [PATCH 102/151] Update TransformationRuleEngineIntegrationTest.groovy Add another accuracy test --- ...sformationRuleEngineIntegrationTest.groovy | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 7f40b1dfe..8ba5c53a9 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -56,20 +56,25 @@ class TransformationRuleEngineIntegrationTest extends Specification { } } - // def "Testing accuracy of rule: convertDemographicsToOrder"() { - // given: - // def bundle = new Bundle() - // engine.ensureRulesLoaded() - // engine.rules.removeAll(engine.rules.findAll { - // it.name != "convertDemographicsToOrder" - // }) - // - // when: - // engine.runRules(new HapiFhirResource(bundle)) - // - // then: - // bundle != null - // } + def "Testing accuracy of rule: convertDemographicsToOrder"() { + given: + def untouchedBundle = new Bundle() + def bundle = new Bundle() + engine.ensureRulesLoaded() + engine.rules.removeAll(engine.rules.findAll { + it.name != "convertDemographicsToOrder" + }) + + when: + engine.runRules(new HapiFhirResource(bundle)) + + then: + untouchedBundle.entry.isEmpty() + bundle.entry.size() == 3 + bundle.entry[0].getResource().fhirType() == "MessageHeader" + bundle.entry[1].getResource().fhirType() == "ServiceRequest" + bundle.entry[2].getResource().fhirType() == "Provenance" + } def "Testing accuracy of rule: addEtorProcessingTag"() { given: @@ -85,7 +90,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: untouchedBundle.entry.isEmpty() - bundle.entry.first().getResource().meta.tag.first().code == "ETOR" + bundle.entry[0].getResource().meta.tag[0].code == "ETOR" } // def "transformation rules filter and run rules for ORM messages"() { From 3bce6fc0b1e4f2b8c0b13a6a20b425e6631a86c2 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 12:50:01 -0700 Subject: [PATCH 103/151] Revert "Added integration test to match each rule to a test file and run it" This reverts commit 7947e86d170b3cc9adca177fc847df922e3f1516. --- .../transformation/TransformationRule.java | 24 +++--- ...sformationRuleEngineIntegrationTest.groovy | 77 +++++++++++++------ 2 files changed, 66 insertions(+), 35 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 3aaaa0cc9..7bd539332 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -38,18 +38,6 @@ public void runRule(FhirResource resource) { this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); } - static Class loadClassFromCache(String className) throws ClassNotFoundException { - return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); - } - - static void executeCustomTransformationMethod( - Class clazz, FhirResource resource, Map args) - throws NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { - Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); - method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); - } - private void applyTransformation( TransformationRuleMethod transformation, FhirResource resource) { String name = transformation.name(); @@ -69,6 +57,10 @@ private void applyTransformation( } } + private static Class loadClassFromCache(String className) throws ClassNotFoundException { + return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); + } + private static Class loadClassByName(String className) { String fullClassName = getFullClassName(className); try { @@ -83,4 +75,12 @@ private static String getFullClassName(String className) { "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; return packageName + "." + className; } + + static void executeCustomTransformationMethod( + Class clazz, FhirResource resource, Map args) + throws NoSuchMethodException, InvocationTargetException, InstantiationException, + IllegalAccessException { + Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); + method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 8b739c898..489996a51 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,7 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation -import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation @@ -49,11 +48,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { def "all transformations in the definitions file have existing custom methods"() { when: - def transformationMethodNames = engine.rules.collectMany { - it.rules.collect { - it.name() - } - } + def transformationMethodNames = engine.rules.collect { rule -> rule.name } then: transformationMethodNames.each { transformationMethodName -> @@ -61,24 +56,60 @@ class TransformationRuleEngineIntegrationTest extends Specification { } } - def "transformation rules run for specific test files and all rules have corresponding test files"() { - given: - Map transformationSampleMap = Map.of( - "addEtorProcessingTag", "e2e/orders/001_OML_O21_short.fhir", - "convertDemographicsToOrder", "e2e/demographics/001_Patient_NBS.fhir", - "convertToOmlOrder", "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir", - "addContactSectionToPatientResource", "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir", - ) + // def "transformation rules filter and run rules for ORM messages"() { + // given: + // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + // def rule = createTransformationRuleFromName(transformationMethodName) + // 0 * mockLogger.logError(_ as String, _ as Exception) + // + // when: + // def testFile = "e2e/orders/001_OML_O21_short.fhir" + // def transformationMethodName = "addContactSectionToPatientResource" + // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + // def transformedFhirResource = addContactSectionToPatientResource(fhirResource) + // + // then: + // def rule = createTransformationRuleFromName(transformationMethodName) + // rule.runRule(fhirResource) + // + // then: + // + //// expect: + //// rule.runRule(fhirResource) + //// + //// where: + //// testFile | transformationMethodName + //// "e2e/orders/001_OML_O21_short.fhir" | "addContactSectionToPatientResource" + // } - engine.rules.each { rule -> - when: - def testFile = transformationSampleMap.get(rule.name) - def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - rule.runRule(fhirResource) + def "transformation rules filter and run rules for OML messages"() { + } - then: - 0 * mockLogger.logError(_ as String, _ as Exception) - 1 * mockLogger.logInfo(_ as String, _ as String) - } + def "transformation rules filter and run rules for ORU messages"() { + } + + def "transformation rules filter and run rules for Demographics"() { + } + + TransformationRule createTransformationRule(List conditions, List transformations) { + return new TransformationRule( + "Rule name", + "Rule description", + "Rule message", + conditions, + transformations, + ) + } + + TransformationRule createTransformationRuleFromName(String transformationName) { + return new TransformationRule( + "Rule name", + "Rule description", + "Rule message", + [], + [ + new TransformationRuleMethod(transformationName, new HashMap()) + ] + ) } } From c3bb3ea0a92b94d2e0229589707c03ccdb845f5c Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 12:59:38 -0700 Subject: [PATCH 104/151] Fixed issues with merge and improved test --- .../etor/ruleengine/RuleEngine.java | 2 + .../transformation/TransformationRule.java | 24 +++--- .../TransformationRuleEngine.java | 8 ++ .../validation/ValidationRuleEngine.java | 8 ++ ...sformationRuleEngineIntegrationTest.groovy | 74 ++++--------------- 5 files changed, 46 insertions(+), 70 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 6357598d8..1fc855d07 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -10,4 +10,6 @@ public interface RuleEngine { void ensureRulesLoaded() throws RuleLoaderException; void runRules(FhirResource resource); + + Rule getRuleByName(String ruleName); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 7bd539332..3aaaa0cc9 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -38,6 +38,18 @@ public void runRule(FhirResource resource) { this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); } + static Class loadClassFromCache(String className) throws ClassNotFoundException { + return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); + } + + static void executeCustomTransformationMethod( + Class clazz, FhirResource resource, Map args) + throws NoSuchMethodException, InvocationTargetException, InstantiationException, + IllegalAccessException { + Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); + method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); + } + private void applyTransformation( TransformationRuleMethod transformation, FhirResource resource) { String name = transformation.name(); @@ -57,10 +69,6 @@ private void applyTransformation( } } - private static Class loadClassFromCache(String className) throws ClassNotFoundException { - return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); - } - private static Class loadClassByName(String className) { String fullClassName = getFullClassName(className); try { @@ -75,12 +83,4 @@ private static String getFullClassName(String className) { "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; return packageName + "." + className; } - - static void executeCustomTransformationMethod( - Class clazz, FhirResource resource, Map args) - throws NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { - Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); - method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); - } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index d29b40b6d..a880e53a8 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -72,4 +72,12 @@ public void runRules(FhirResource resource) { } }); } + + @Override + public TransformationRule getRuleByName(String ruleName) { + return rules.stream() + .filter(rule -> rule.getName().equals(ruleName)) + .findFirst() + .orElse(null); + } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java index 45f15abb9..b0af9cab7 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java @@ -65,4 +65,12 @@ public void runRules(FhirResource resource) { } } } + + @Override + public ValidationRule getRuleByName(String ruleName) { + return rules.stream() + .filter(rule -> rule.getName().equals(ruleName)) + .findFirst() + .orElse(null); + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 8ba5c53a9..5339b5592 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,6 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation - +import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation @@ -55,6 +55,21 @@ class TransformationRuleEngineIntegrationTest extends Specification { assert TransformationRule.loadClassFromCache(transformationMethodName) != null } } + def "transformation rules run for specific test files and all rules have corresponding test files"() { + def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + 0 * mockLogger.logError(_ as String, _ as Exception) + 1 * mockLogger.logInfo(_ as String, _ as String) + + expect: + engine.getRuleByName(ruleName).runRule(fhirResource) + + where: + ruleName | testFile + "addEtorProcessingTag" | "e2e/orders/001_OML_O21_short.fhir" + "convertDemographicsToOrder" | "e2e/demographics/001_Patient_NBS.fhir" + "convertToOmlOrder" | "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" + "addContactSectionToPatientResource" | "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" + } def "Testing accuracy of rule: convertDemographicsToOrder"() { given: @@ -92,61 +107,4 @@ class TransformationRuleEngineIntegrationTest extends Specification { untouchedBundle.entry.isEmpty() bundle.entry[0].getResource().meta.tag[0].code == "ETOR" } - - // def "transformation rules filter and run rules for ORM messages"() { - // given: - // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - // def rule = createTransformationRuleFromName(transformationMethodName) - // 0 * mockLogger.logError(_ as String, _ as Exception) - // - // when: - // def testFile = "e2e/orders/001_OML_O21_short.fhir" - // def transformationMethodName = "addContactSectionToPatientResource" - // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - // def transformedFhirResource = addContactSectionToPatientResource(fhirResource) - // - // then: - // def rule = createTransformationRuleFromName(transformationMethodName) - // rule.runRule(fhirResource) - // - // then: - // - //// expect: - //// rule.runRule(fhirResource) - //// - //// where: - //// testFile | transformationMethodName - //// "e2e/orders/001_OML_O21_short.fhir" | "addContactSectionToPatientResource" - // } - - def "transformation rules filter and run rules for OML messages"() { - } - - def "transformation rules filter and run rules for ORU messages"() { - } - - def "transformation rules filter and run rules for Demographics"() { - } - - TransformationRule createTransformationRule(List conditions, List transformations) { - return new TransformationRule( - "Rule name", - "Rule description", - "Rule message", - conditions, - transformations, - ) - } - - TransformationRule createTransformationRuleFromName(String transformationName) { - return new TransformationRule( - "Rule name", - "Rule description", - "Rule message", - [], - [ - new TransformationRuleMethod(transformationName, new HashMap()) - ] - ) - } } From 78b3360c5f0aa9ba76e4d4a60d0bfd69331e9e4c Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 2 May 2024 16:12:44 -0400 Subject: [PATCH 105/151] Update TransformationRuleEngineIntegrationTest.groovy Additional test cases --- ...sformationRuleEngineIntegrationTest.groovy | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 8ba5c53a9..bfbec557c 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,6 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation - +import gov.hhs.cdc.trustedintermediary.FhirBundleHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation @@ -11,6 +11,7 @@ import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.Patient import spock.lang.Specification class TransformationRuleEngineIntegrationTest extends Specification { @@ -59,7 +60,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { def "Testing accuracy of rule: convertDemographicsToOrder"() { given: def untouchedBundle = new Bundle() - def bundle = new Bundle() + def bundle = untouchedBundle.copy() engine.ensureRulesLoaded() engine.rules.removeAll(engine.rules.findAll { it.name != "convertDemographicsToOrder" @@ -79,7 +80,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { def "Testing accuracy of rule: addEtorProcessingTag"() { given: def untouchedBundle = new Bundle() - def bundle = new Bundle() + def bundle = untouchedBundle.copy() engine.ensureRulesLoaded() engine.rules.removeAll(engine.rules.findAll { it.name != "addEtorProcessingTag" @@ -93,6 +94,43 @@ class TransformationRuleEngineIntegrationTest extends Specification { bundle.entry[0].getResource().meta.tag[0].code == "ETOR" } + def "Testing accuracy of rule: convertToOmlOrder"() { + given: + def untouchedBundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "ORM_O01") + def bundle = untouchedBundle.copy() + engine.ensureRulesLoaded() + engine.rules.removeAll(engine.rules.findAll { + it.name != "convertToOmlOrder" + }) + + when: + engine.runRules(new HapiFhirResource(bundle)) + + then: + untouchedBundle.entry[0].getResource().event.code == "O01" + bundle.entry[0].getResource().event.code == "O21" + } + + def "Testing accuracy of rule: addContactSectionToPatientResource"() { + given: + def untouchedBundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "OML_O21") + untouchedBundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) + def bundle = untouchedBundle.copy() + engine.ensureRulesLoaded() + engine.rules.removeAll(engine.rules.findAll { + it.name != "addContactSectionToPatientResource" + }) + + when: + engine.runRules(new HapiFhirResource(bundle)) + + then: + def untouchedPatient = untouchedBundle.entry[2].getResource() as Patient + untouchedPatient.contact.isEmpty() + def patient = bundle.entry[2].getResource() as Patient + patient.contact.size() > 0 + } + // def "transformation rules filter and run rules for ORM messages"() { // given: // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) From 9b22430e520ac76d3d4beb2083f20d58fe2c5cc3 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 15:47:07 -0700 Subject: [PATCH 106/151] Added tests for getRuleByName implementation --- .../TransformationRuleEngineTest.groovy | 16 +++++++++++++++- .../validation/ValidationRuleEngineTest.groovy | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy index 791f76eee..5e4468b4d 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy @@ -123,7 +123,6 @@ class TransformationRuleEngineTest extends Specification { 0 * mockLogger.logInfo(applyingTransformationMessage) } - def "runRules logs an error and doesn't run any rules when there's a RuleLoaderException"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) @@ -135,4 +134,19 @@ class TransformationRuleEngineTest extends Specification { then: 1 * mockLogger.logError(_ as String, exception) } + + def "getRuleByName returns the rule with the given name"() { + given: + def ruleName = "Rule name" + def testRule = Mock(TransformationRule) + testRule.getName() >> ruleName + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [testRule] + + when: + ruleEngine.ensureRulesLoaded() + def rule = ruleEngine.getRuleByName(ruleName) + + then: + rule == testRule + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy index 6178b5af9..8caa9a168 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy @@ -124,7 +124,6 @@ class ValidationRuleEngineTest extends Specification { 0 * mockLogger.logWarning(fullFailedValidationMessage) } - def "runRules logs an error and doesn't run any rules when there's a RuleLoaderException"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) @@ -136,4 +135,19 @@ class ValidationRuleEngineTest extends Specification { then: 1 * mockLogger.logError(_ as String, exception) } + + def "getRuleByName returns the rule with the given name"() { + given: + def ruleName = "Rule name" + def testRule = Mock(ValidationRule) + testRule.getName() >> ruleName + mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [testRule] + + when: + ruleEngine.ensureRulesLoaded() + def rule = ruleEngine.getRuleByName(ruleName) + + then: + rule == testRule + } } From f722ad601dfa424adf6479f1146f1724ce25ab99 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 16:14:52 -0700 Subject: [PATCH 107/151] Removed unused code --- ...sformationRuleEngineIntegrationTest.groovy | 60 +------------------ 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 0b9840d64..07aeec0fa 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -59,6 +59,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { } def "transformation rules run for specific test files and all rules have corresponding test files"() { + given: def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) 0 * mockLogger.logError(_ as String, _ as Exception) 1 * mockLogger.logInfo(_ as String, _ as String) @@ -69,9 +70,9 @@ class TransformationRuleEngineIntegrationTest extends Specification { where: ruleName | testFile "addEtorProcessingTag" | "e2e/orders/001_OML_O21_short.fhir" - "convertDemographicsToOrder" | "e2e/demographics/001_Patient_NBS.fhir" "convertToOmlOrder" | "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" "addContactSectionToPatientResource" | "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" + "convertDemographicsToOrder" | "e2e/demographics/001_Patient_NBS.fhir" } def "Testing accuracy of rule: convertDemographicsToOrder"() { @@ -147,61 +148,4 @@ class TransformationRuleEngineIntegrationTest extends Specification { def patient = bundle.entry[2].getResource() as Patient patient.contact.size() > 0 } - - // def "transformation rules filter and run rules for ORM messages"() { - // given: - // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - // def rule = createTransformationRuleFromName(transformationMethodName) - // 0 * mockLogger.logError(_ as String, _ as Exception) - // - // when: - // def testFile = "e2e/orders/001_OML_O21_short.fhir" - // def transformationMethodName = "addContactSectionToPatientResource" - // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - // def transformedFhirResource = addContactSectionToPatientResource(fhirResource) - // - // then: - // def rule = createTransformationRuleFromName(transformationMethodName) - // rule.runRule(fhirResource) - // - // then: - // - //// expect: - //// rule.runRule(fhirResource) - //// - //// where: - //// testFile | transformationMethodName - //// "e2e/orders/001_OML_O21_short.fhir" | "addContactSectionToPatientResource" - // } - - def "transformation rules filter and run rules for OML messages"() { - } - - def "transformation rules filter and run rules for ORU messages"() { - } - - def "transformation rules filter and run rules for Demographics"() { - } - - TransformationRule createTransformationRule(List conditions, List transformations) { - return new TransformationRule( - "Rule name", - "Rule description", - "Rule message", - conditions, - transformations, - ) - } - - TransformationRule createTransformationRuleFromName(String transformationName) { - return new TransformationRule( - "Rule name", - "Rule description", - "Rule message", - [], - [ - new TransformationRuleMethod(transformationName, new HashMap()) - ] - ) - } } From 386849b54ca99cbb51d88844a9b5481414f84523 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 2 May 2024 16:35:08 -0700 Subject: [PATCH 108/151] Fixed integration test --- .../TransformationRuleEngineIntegrationTest.groovy | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 07aeec0fa..275bbd135 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -32,8 +32,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.injectRegisteredImplementations() - - engine.ensureRulesLoaded() } def "transformation rules run without error"() { @@ -60,12 +58,16 @@ class TransformationRuleEngineIntegrationTest extends Specification { def "transformation rules run for specific test files and all rules have corresponding test files"() { given: + engine.ensureRulesLoaded() def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + def fhirBundle = (Bundle) fhirResource.getUnderlyingResource() + def fhirBundleCopy = fhirBundle.copy() + def rule = engine.getRuleByName(ruleName) 0 * mockLogger.logError(_ as String, _ as Exception) - 1 * mockLogger.logInfo(_ as String, _ as String) expect: - engine.getRuleByName(ruleName).runRule(fhirResource) + rule.runRule(fhirResource) + !fhirBundle.equalsDeep(fhirBundleCopy) where: ruleName | testFile From 2a06236c162aba151b4bdb3c596088506813d7ad Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Fri, 3 May 2024 00:53:57 -0700 Subject: [PATCH 109/151] refactor: deleted redundant if statement --- .../TransformationRuleEngine.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index a880e53a8..f8d59f4ff 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -36,21 +36,19 @@ public void unloadRules() { @Override public void ensureRulesLoaded() throws RuleLoaderException { - if (rules.isEmpty()) { - synchronized (this) { - if (rules.isEmpty()) { - String path = ruleDefinitionsFileName; - try (InputStream resourceStream = - getClass().getClassLoader().getResourceAsStream(path)) { - if (resourceStream == null) { - throw new RuleLoaderException("No resource found at " + path); - } - List parsedRules = - ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); - this.rules.addAll(parsedRules); - } catch (IOException e) { - throw new RuleLoaderException("Failed to load rules from " + path, e); + synchronized (this) { + if (rules.isEmpty()) { + String path = ruleDefinitionsFileName; + try (InputStream resourceStream = + getClass().getClassLoader().getResourceAsStream(path)) { + if (resourceStream == null) { + throw new RuleLoaderException("No resource found at " + path); } + List parsedRules = + ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); + this.rules.addAll(parsedRules); + } catch (IOException e) { + throw new RuleLoaderException("Failed to load rules from " + path, e); } } } From b75a8e34360ac7583385c676f0a7063f0ffd77b6 Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Fri, 3 May 2024 02:02:59 -0700 Subject: [PATCH 110/151] refactor: don't stop transormation if a rule fails --- .../transformation/TransformationRuleEngine.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index f8d59f4ff..90eeddde0 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -65,8 +65,12 @@ public void runRules(FhirResource resource) { rules.forEach( rule -> { - if (rule.shouldRun(resource)) { - rule.runRule((resource)); + try { + if (rule.shouldRun(resource)) { + rule.runRule((resource)); + } + } catch (Exception e) { + logger.logError("Error executing rule: " + rule.getName(), e); } }); } From d1664b5d65e7da9942eb7b334ae4c80dbce895c7 Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Fri, 3 May 2024 02:08:13 -0700 Subject: [PATCH 111/151] revert try catch, there is no excepton thrown when rules fail --- .../transformation/TransformationRuleEngine.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 90eeddde0..f8d59f4ff 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -65,12 +65,8 @@ public void runRules(FhirResource resource) { rules.forEach( rule -> { - try { - if (rule.shouldRun(resource)) { - rule.runRule((resource)); - } - } catch (Exception e) { - logger.logError("Error executing rule: " + rule.getName(), e); + if (rule.shouldRun(resource)) { + rule.runRule((resource)); } }); } From 46bd8efd48883af69d2ba9281c628351509bce2c Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Fri, 3 May 2024 02:56:23 -0700 Subject: [PATCH 112/151] Refactor: use ConcurrentHashMap --- .../etor/ruleengine/transformation/TransformationRule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 3aaaa0cc9..2ce63ddfe 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -4,9 +4,9 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * The TransformationRule class extends the {@link @@ -17,7 +17,7 @@ */ public class TransformationRule extends Rule { - private static final Map> classCache = new HashMap<>(); + private static final Map> classCache = new ConcurrentHashMap<>(); /** * Do not delete this constructor! It is used for JSON deserialization when loading rules from a From 7d68b8d33a17d8f7fad2d85c60e207ab4df2bf01 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Fri, 3 May 2024 11:35:18 -0400 Subject: [PATCH 113/151] Added double-checked locking with comment explaining it --- .../TransformationRuleEngine.java | 27 ++++++++++--------- .../validation/ValidationRuleEngine.java | 1 + 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index f8d59f4ff..a18868a5e 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -36,19 +36,22 @@ public void unloadRules() { @Override public void ensureRulesLoaded() throws RuleLoaderException { - synchronized (this) { - if (rules.isEmpty()) { - String path = ruleDefinitionsFileName; - try (InputStream resourceStream = - getClass().getClassLoader().getResourceAsStream(path)) { - if (resourceStream == null) { - throw new RuleLoaderException("No resource found at " + path); + // Double-checked locking - needed to protect from excessive sync locks + if (rules.isEmpty()) { + synchronized (this) { + if (rules.isEmpty()) { + String path = ruleDefinitionsFileName; + try (InputStream resourceStream = + getClass().getClassLoader().getResourceAsStream(path)) { + if (resourceStream == null) { + throw new RuleLoaderException("No resource found at " + path); + } + List parsedRules = + ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); + this.rules.addAll(parsedRules); + } catch (IOException e) { + throw new RuleLoaderException("Failed to load rules from " + path, e); } - List parsedRules = - ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); - this.rules.addAll(parsedRules); - } catch (IOException e) { - throw new RuleLoaderException("Failed to load rules from " + path, e); } } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java index b0af9cab7..f1dbbc931 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java @@ -35,6 +35,7 @@ public void unloadRules() { @Override public void ensureRulesLoaded() throws RuleLoaderException { + // Double-checked locking - needed to protect from excessive sync locks if (rules.isEmpty()) { synchronized (this) { if (rules.isEmpty()) { From 9301667fec79d9517c81c01212ffc9a8ba694d98 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 08:39:35 -0700 Subject: [PATCH 114/151] Merged with main --- README.md | 2 ++ etor/build.gradle | 2 +- shared/build.gradle | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 62c80fe48..ae10cdfbb 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,8 @@ with this option enabled. 2. Copy the scripts found at [/scripts/rs](/scripts/rs) to `prime-reportstream/prime-router` - **Note**: follow the instructions in [/scripts/rs/readme.md](/scripts/rs/readme.md) to set up the environment variable 3. CD to `prime-reportstream/prime-router` + + If attempting to access the metadata endpoint in ReportStream add the variable `ETOR_TI_baseurl="http://host.docker.internal:8080"` to `.prime-router/.vault/env/.env.local` file before building the container 4. Run the `./cleanslate` script. For more information you can refer to the [ReportStream docs](https://github.com/CDCgov/prime-reportstream/blob/master/prime-router/docs/docs-deprecated/getting-started/getting-started.md#building-the-baseline) 5. Run RS with `docker compose up --build -d` 6. Edit `/settings/staging/0166-flexion-staging-results-handling.yml` to comment the lines related to staging settings, and uncomment the ones for local settings: diff --git a/etor/build.gradle b/etor/build.gradle index d53924f09..a276f2ca9 100644 --- a/etor/build.gradle +++ b/etor/build.gradle @@ -14,7 +14,7 @@ dependencies { testImplementation testFixtures(project(':shared')) implementation 'com.azure:azure-storage-blob:12.25.4' - implementation 'com.azure:azure-identity:1.12.0' + implementation 'com.azure:azure-identity:1.12.1' testImplementation 'org.apache.groovy:groovy:4.0.21' testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0' diff --git a/shared/build.gradle b/shared/build.gradle index a75cca3ae..f6edec590 100644 --- a/shared/build.gradle +++ b/shared/build.gradle @@ -45,7 +45,7 @@ dependencies { // azure sdk implementation 'com.azure:azure-security-keyvault-secrets:4.8.2' - implementation 'com.azure:azure-identity:1.12.0' + implementation 'com.azure:azure-identity:1.12.1' testImplementation 'org.apache.groovy:groovy:4.0.21' testFixturesImplementation 'org.apache.groovy:groovy:4.0.21' From 0e4bb530351b1269f2005b5fa6da68ede63cde0f Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 09:46:31 -0700 Subject: [PATCH 115/151] Getting new instance of the custom class using the interface --- .../etor/ruleengine/transformation/TransformationRule.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 2ce63ddfe..c8c88cee0 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -3,7 +3,6 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -46,8 +45,9 @@ static void executeCustomTransformationMethod( Class clazz, FhirResource resource, Map args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - Method method = clazz.getDeclaredMethod("transform", FhirResource.class, Map.class); - method.invoke(clazz.getDeclaredConstructor().newInstance(), resource, args); + CustomFhirTransformation transformation = + (CustomFhirTransformation) clazz.getDeclaredConstructor().newInstance(); + transformation.transform(resource, args); } private void applyTransformation( From 97150881a6107ba0524e6a41589394e0ad2c4a25 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Fri, 3 May 2024 12:06:43 -0500 Subject: [PATCH 116/151] Update TransformationRule exception being thrown --- .../etor/ruleengine/transformation/TransformationRule.java | 4 +--- .../trustedintermediary/external/hapi/HapiTestHelper.groovy | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiTestHelper.groovy diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 2ce63ddfe..3a3f3abbb 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -38,7 +38,7 @@ public void runRule(FhirResource resource) { this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); } - static Class loadClassFromCache(String className) throws ClassNotFoundException { + static Class loadClassFromCache(String className) throws RuntimeException { return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); } @@ -64,8 +64,6 @@ private void applyTransformation( | InvocationTargetException | InstantiationException e) { logger.logError("Error invoking method: " + name + ", due to: " + e.getMessage(), e); - } catch (ClassNotFoundException e) { - logger.logError("Transformation class not found: " + name, e); } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiTestHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiTestHelper.groovy new file mode 100644 index 000000000..176d3b542 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiTestHelper.groovy @@ -0,0 +1,4 @@ +package gov.hhs.cdc.trustedintermediary.external.hapi + +class HapiTestHelper { +} From 3c092dcb5f07bd341090f8b5009b776713b10219 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 10:58:18 -0700 Subject: [PATCH 117/151] Refactored integration tests and removed test that is already covered --- .../FhirBundleHelper.groovy | 8 ++ ...sformationRuleEngineIntegrationTest.groovy | 124 +++++++++--------- 2 files changed, 70 insertions(+), 62 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy index a4bdededd..f9b22cd50 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy @@ -5,6 +5,7 @@ import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.MessageHeader import org.hl7.fhir.r4.model.Organization import org.hl7.fhir.r4.model.Reference +import org.hl7.fhir.r4.model.Resource class FhirBundleHelper { @@ -30,4 +31,11 @@ class FhirBundleHelper { bundle.addEntry().setFullUrl(receiverOrganizationFullUrl).setResource(receiverOrganization) return bundle } + + static resourceInBundle(Bundle bundle, Class resourceType) { + return bundle.entry.stream() + .map { it.resource } + .filter { it.class == resourceType } + .findFirst().orElse(null) + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 275bbd135..298a84d01 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -12,7 +12,10 @@ import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.MessageHeader import org.hl7.fhir.r4.model.Patient +import org.hl7.fhir.r4.model.ServiceRequest +import org.hl7.fhir.r4.model.Provenance import spock.lang.Specification class TransformationRuleEngineIntegrationTest extends Specification { @@ -56,98 +59,95 @@ class TransformationRuleEngineIntegrationTest extends Specification { } } - def "transformation rules run for specific test files and all rules have corresponding test files"() { + def "test rule transformation accuracy: addEtorProcessingTag"() { given: - engine.ensureRulesLoaded() - def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - def fhirBundle = (Bundle) fhirResource.getUnderlyingResource() - def fhirBundleCopy = fhirBundle.copy() - def rule = engine.getRuleByName(ruleName) - 0 * mockLogger.logError(_ as String, _ as Exception) - - expect: - rule.runRule(fhirResource) - !fhirBundle.equalsDeep(fhirBundleCopy) - - where: - ruleName | testFile - "addEtorProcessingTag" | "e2e/orders/001_OML_O21_short.fhir" - "convertToOmlOrder" | "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" - "addContactSectionToPatientResource" | "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" - "convertDemographicsToOrder" | "e2e/demographics/001_Patient_NBS.fhir" - } + def ruleName = "addEtorProcessingTag" + // we could also use this file for testing the rule: e2e/orders/001_OML_O21_short.fhir + def bundle = new Bundle() + def untouchedBundle = bundle.copy() - def "Testing accuracy of rule: convertDemographicsToOrder"() { - given: - def untouchedBundle = new Bundle() - def bundle = untouchedBundle.copy() engine.ensureRulesLoaded() - engine.rules.removeAll(engine.rules.findAll { - it.name != "convertDemographicsToOrder" - }) + def rule = engine.getRuleByName(ruleName) when: - engine.runRules(new HapiFhirResource(bundle)) + rule.runRule(new HapiFhirResource(bundle)) + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader.class) then: - untouchedBundle.entry.isEmpty() - bundle.entry.size() == 3 - bundle.entry[0].getResource().fhirType() == "MessageHeader" - bundle.entry[1].getResource().fhirType() == "ServiceRequest" - bundle.entry[2].getResource().fhirType() == "Provenance" + 0 * mockLogger.logError(_ as String, _ as Exception) + !bundle.equalsDeep(untouchedBundle) + messageHeader.meta.tag.last().code == "ETOR" } - def "Testing accuracy of rule: addEtorProcessingTag"() { + def "test rule transformation accuracy: convertToOmlOrder"() { given: - def untouchedBundle = new Bundle() - def bundle = untouchedBundle.copy() + def ruleName = "convertToOmlOrder" + // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir + def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "ORM_O01") + def untouchedBundle = bundle.copy() + engine.ensureRulesLoaded() - engine.rules.removeAll(engine.rules.findAll { - it.name != "addEtorProcessingTag" - }) + def rule = engine.getRuleByName(ruleName) when: - engine.runRules(new HapiFhirResource(bundle)) + rule.runRule(new HapiFhirResource(bundle)) + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader.class) + def untouchedMessageHeader = FhirBundleHelper.resourceInBundle(untouchedBundle, MessageHeader.class) then: - untouchedBundle.entry.isEmpty() - bundle.entry[0].getResource().meta.tag.last().code == "ETOR" + 0 * mockLogger.logError(_ as String, _ as Exception) + !bundle.equalsDeep(untouchedBundle) + untouchedMessageHeader.event.code == "O01" + messageHeader.event.code == "O21" } - def "Testing accuracy of rule: convertToOmlOrder"() { + def "test rule transformation accuracy: addContactSectionToPatientResource"() { given: - def untouchedBundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "ORM_O01") - def bundle = untouchedBundle.copy() + def ruleName = "addContactSectionToPatientResource" + + // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir + def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "OML_O21") + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) + def untouchedBundle = bundle.copy() + engine.ensureRulesLoaded() - engine.rules.removeAll(engine.rules.findAll { - it.name != "convertToOmlOrder" - }) + def rule = engine.getRuleByName(ruleName) when: - engine.runRules(new HapiFhirResource(bundle)) + rule.runRule(new HapiFhirResource(bundle)) + def patient = FhirBundleHelper.resourceInBundle(bundle, Patient.class) + def untouchedPatient = FhirBundleHelper.resourceInBundle(untouchedBundle, Patient.class) then: - untouchedBundle.entry[0].getResource().event.code == "O01" - bundle.entry[0].getResource().event.code == "O21" + 0 * mockLogger.logError(_ as String, _ as Exception) + !bundle.equalsDeep(untouchedBundle) + untouchedPatient.contact.isEmpty() + patient.contact.size() > 0 } - def "Testing accuracy of rule: addContactSectionToPatientResource"() { + def "test rule transformation accuracy: convertDemographicsToOrder"() { given: - def untouchedBundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "OML_O21") - untouchedBundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) - def bundle = untouchedBundle.copy() + def ruleName = "convertDemographicsToOrder" + def testFile = "e2e/demographics/001_Patient_NBS.fhir" + + def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + def bundle = (Bundle) fhirResource.getUnderlyingResource() + def untouchedBundle = bundle.copy() + engine.ensureRulesLoaded() - engine.rules.removeAll(engine.rules.findAll { - it.name != "addContactSectionToPatientResource" - }) + def rule = engine.getRuleByName(ruleName) when: - engine.runRules(new HapiFhirResource(bundle)) + rule.runRule(fhirResource) + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader.class) + def serviceRequest = FhirBundleHelper.resourceInBundle(bundle, ServiceRequest.class) + def provenance = FhirBundleHelper.resourceInBundle(bundle, Provenance.class) then: - def untouchedPatient = untouchedBundle.entry[2].getResource() as Patient - untouchedPatient.contact.isEmpty() - def patient = bundle.entry[2].getResource() as Patient - patient.contact.size() > 0 + 0 * mockLogger.logError(_ as String, _ as Exception) + !bundle.equalsDeep(untouchedBundle) + messageHeader != null + serviceRequest != null + provenance != null } } From e271ce20666d49112364ff70af62bea19efb7059 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 12:08:57 -0700 Subject: [PATCH 118/151] Added missing override tag --- .../etor/ruleengine/transformation/TransformationRule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index c8c88cee0..4db349a14 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -33,6 +33,7 @@ public TransformationRule( super(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions); } + @Override public void runRule(FhirResource resource) { this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); } From 128179034f8e2bdc69fbbb9a8efe0a3bcc17acc6 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 12:27:56 -0700 Subject: [PATCH 119/151] Fixed test helper logic --- .../trustedintermediary/FhirBundleHelper.groovy | 2 +- .../TransformationRuleEngineIntegrationTest.groovy | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy index f9b22cd50..bdfea3e55 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy @@ -34,7 +34,7 @@ class FhirBundleHelper { static resourceInBundle(Bundle bundle, Class resourceType) { return bundle.entry.stream() - .map { it.resource } + .map { it.getResource() } .filter { it.class == resourceType } .findFirst().orElse(null) } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 298a84d01..a73d95979 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -71,7 +71,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: rule.runRule(new HapiFhirResource(bundle)) - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader.class) + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) then: 0 * mockLogger.logError(_ as String, _ as Exception) @@ -91,8 +91,8 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: rule.runRule(new HapiFhirResource(bundle)) - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader.class) - def untouchedMessageHeader = FhirBundleHelper.resourceInBundle(untouchedBundle, MessageHeader.class) + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) + def untouchedMessageHeader = FhirBundleHelper.resourceInBundle(untouchedBundle, MessageHeader) then: 0 * mockLogger.logError(_ as String, _ as Exception) @@ -116,7 +116,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: rule.runRule(new HapiFhirResource(bundle)) def patient = FhirBundleHelper.resourceInBundle(bundle, Patient.class) - def untouchedPatient = FhirBundleHelper.resourceInBundle(untouchedBundle, Patient.class) + def untouchedPatient = FhirBundleHelper.resourceInBundle(untouchedBundle, Patient) then: 0 * mockLogger.logError(_ as String, _ as Exception) @@ -139,9 +139,9 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: rule.runRule(fhirResource) - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader.class) - def serviceRequest = FhirBundleHelper.resourceInBundle(bundle, ServiceRequest.class) - def provenance = FhirBundleHelper.resourceInBundle(bundle, Provenance.class) + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) + def serviceRequest = FhirBundleHelper.resourceInBundle(bundle, ServiceRequest) + def provenance = FhirBundleHelper.resourceInBundle(bundle, Provenance) then: 0 * mockLogger.logError(_ as String, _ as Exception) From ef2ab1b59a37b587d7c77c9c5adb4384b4718e07 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 12:32:27 -0700 Subject: [PATCH 120/151] Ignoring demographics transformation for now --- .../e2e/DemographicsTest.groovy | 37 +++++++------- .../resources/transformation_definitions.json | 14 ----- ...sformationRuleEngineIntegrationTest.groovy | 51 ++++++++++--------- 3 files changed, 45 insertions(+), 57 deletions(-) diff --git a/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy b/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy index 389650e6d..efc36d781 100644 --- a/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy +++ b/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy @@ -29,24 +29,25 @@ class DemographicsTest extends Specification { parsedJsonBody.patientId == expectedPatientId } - def "payload file check"() { - when: - def response = demographicsClient.submit(newbornPatientJsonFileString, true) - def parsedResponseBody = JsonParser.parseContent(response) - def sentPayload = SentPayloadReader.read() - def parsedSentPayload = JsonParser.parse(sentPayload) - - then: - response.getCode() == 200 - parsedSentPayload.entry[0].resource.resourceType == "MessageHeader" - parsedSentPayload.entry[2].resource.resourceType == "ServiceRequest" - - parsedSentPayload.entry[1].resource.resourceType == "Patient" - parsedSentPayload.entry[1].resource.id == "infant-twin-1" - - parsedSentPayload.entry[1].resource.identifier[1].value == parsedResponseBody.patientId //the second (index 1) identifier so happens to be the MRN - parsedSentPayload.resourceType + "/" + parsedSentPayload.id == parsedResponseBody.fhirResourceId - } + // todo: ignoring while figuring out how to filter the demographics example + // def "payload file check"() { + // when: + // def response = demographicsClient.submit(newbornPatientJsonFileString, true) + // def parsedResponseBody = JsonParser.parseContent(response) + // def sentPayload = SentPayloadReader.read() + // def parsedSentPayload = JsonParser.parse(sentPayload) + // + // then: + // response.getCode() == 200 + // parsedSentPayload.entry[0].resource.resourceType == "MessageHeader" + // parsedSentPayload.entry[2].resource.resourceType == "ServiceRequest" + // + // parsedSentPayload.entry[1].resource.resourceType == "Patient" + // parsedSentPayload.entry[1].resource.id == "infant-twin-1" + // + // parsedSentPayload.entry[1].resource.identifier[1].value == parsedResponseBody.patientId //the second (index 1) identifier so happens to be the MRN + // parsedSentPayload.resourceType + "/" + parsedSentPayload.id == parsedResponseBody.fhirResourceId + // } def "return a 400 response when request has unexpected format"() { given: diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 94f937553..6fbbd07d2 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -1,19 +1,5 @@ { "definitions": [ { - "name": "convertDemographicsToOrder", - "description": "Convert Demographics to Order", - "message": "", - "conditions": [ - "Bundle.entry.resource.all($this is Patient)" - ], - "rules": [ - { - "name": "convertDemographicsToOrder", - "args": { } - } - ] - }, - { "name": "addEtorProcessingTag", "description": "Add ETOR Processing Tag", "message": "", diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index a73d95979..ca47e67ab 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -125,29 +125,30 @@ class TransformationRuleEngineIntegrationTest extends Specification { patient.contact.size() > 0 } - def "test rule transformation accuracy: convertDemographicsToOrder"() { - given: - def ruleName = "convertDemographicsToOrder" - def testFile = "e2e/demographics/001_Patient_NBS.fhir" - - def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - def bundle = (Bundle) fhirResource.getUnderlyingResource() - def untouchedBundle = bundle.copy() - - engine.ensureRulesLoaded() - def rule = engine.getRuleByName(ruleName) - - when: - rule.runRule(fhirResource) - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) - def serviceRequest = FhirBundleHelper.resourceInBundle(bundle, ServiceRequest) - def provenance = FhirBundleHelper.resourceInBundle(bundle, Provenance) - - then: - 0 * mockLogger.logError(_ as String, _ as Exception) - !bundle.equalsDeep(untouchedBundle) - messageHeader != null - serviceRequest != null - provenance != null - } + // todo: ignoring while figuring out how to filter the demographics example + // def "test rule transformation accuracy: convertDemographicsToOrder"() { + // given: + // def ruleName = "convertDemographicsToOrder" + // def testFile = "e2e/demographics/001_Patient_NBS.fhir" + // + // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + // def bundle = (Bundle) fhirResource.getUnderlyingResource() + // def untouchedBundle = bundle.copy() + // + // engine.ensureRulesLoaded() + // def rule = engine.getRuleByName(ruleName) + // + // when: + // rule.runRule(fhirResource) + // def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) + // def serviceRequest = FhirBundleHelper.resourceInBundle(bundle, ServiceRequest) + // def provenance = FhirBundleHelper.resourceInBundle(bundle, Provenance) + // + // then: + // 0 * mockLogger.logError(_ as String, _ as Exception) + // !bundle.equalsDeep(untouchedBundle) + // messageHeader != null + // serviceRequest != null + // provenance != null + // } } From d0c5549b4e041d0d8a21fd6c8118a546abb21efd Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 12:37:02 -0700 Subject: [PATCH 121/151] Removed custom exception constructor overload --- .../etor/ruleengine/RuleLoaderException.java | 5 ----- .../ruleengine/transformation/TransformationRuleEngine.java | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderException.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderException.java index 290ced3e5..c644f17bf 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderException.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderException.java @@ -2,12 +2,7 @@ /** Custom exception class use to catch RuleLoader exceptions */ public class RuleLoaderException extends Exception { - public RuleLoaderException(String message, Throwable cause) { super(message, cause); } - - public RuleLoaderException(String message) { - super(message); - } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index a18868a5e..86f5973ba 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -44,7 +44,8 @@ public void ensureRulesLoaded() throws RuleLoaderException { try (InputStream resourceStream = getClass().getClassLoader().getResourceAsStream(path)) { if (resourceStream == null) { - throw new RuleLoaderException("No resource found at " + path); + throw new RuleLoaderException( + "No resource found at " + path, new IOException()); } List parsedRules = ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); From 7ce3dd3b52ffed8f82a47d04c0df8d485d7e420c Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Fri, 3 May 2024 14:42:28 -0500 Subject: [PATCH 122/151] Update TransformationRuleTest coverage --- .../transformation/TransformationRule.java | 26 ++--- .../TransformationRuleTest.groovy | 109 +++++++++++++++++- .../custom/HappyPathMockClass.groovy | 24 ++++ .../IllegalAccessExceptionMockClass.groovy | 13 +++ .../InstantiationExceptionMockClass.groovy | 4 + .../NoSuchMethodExceptionMockClass.groovy | 15 +++ .../external/hapi/HapiTestHelper.groovy | 8 ++ 7 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/HappyPathMockClass.groovy create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/IllegalAccessExceptionMockClass.groovy create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/InstantiationExceptionMockClass.groovy create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/NoSuchMethodExceptionMockClass.groovy diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index ee41c7072..fef339b78 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -37,19 +37,6 @@ public void runRule(FhirResource resource) { this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); } - static Class loadClassFromCache(String className) throws RuntimeException { - return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); - } - - static void executeCustomTransformationMethod( - Class clazz, FhirResource resource, Map args) - throws NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { - CustomFhirTransformation transformation = - (CustomFhirTransformation) clazz.getDeclaredConstructor().newInstance(); - transformation.transform(resource, args); - } - private void applyTransformation( TransformationRuleMethod transformation, FhirResource resource) { String name = transformation.name(); @@ -67,6 +54,10 @@ private void applyTransformation( } } + static Class loadClassFromCache(String className) throws RuntimeException { + return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); + } + private static Class loadClassByName(String className) { String fullClassName = getFullClassName(className); try { @@ -81,4 +72,13 @@ private static String getFullClassName(String className) { "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; return packageName + "." + className; } + + static void executeCustomTransformationMethod( + Class clazz, FhirResource resource, Map args) + throws NoSuchMethodException, InvocationTargetException, InstantiationException, + IllegalAccessException { + CustomFhirTransformation transformation = + (CustomFhirTransformation) clazz.getDeclaredConstructor().newInstance(); + transformation.transform(resource, args); + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy index cb1622dff..aab55b357 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy @@ -1,8 +1,13 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation +import gov.hhs.cdc.trustedintermediary.FhirBundleHelper +import gov.hhs.cdc.trustedintermediary.FhirResourceMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiTestHelper import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.Logger +import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.MessageHeader import spock.lang.Specification class TransformationRuleTest extends Specification { @@ -39,6 +44,108 @@ class TransformationRuleTest extends Specification { rule.getRules() == ruleActions } - def "runRule() works correctly"() { + def "runRule() performs a transformation rule when one exists"() { + given: + def ruleName = "Rule name" + def ruleDescription = "Rule Description" + def ruleMessage = "Rule Warning Message" + def ruleConditions = ["condition1", "condition2"] + def ruleActions = [ + new TransformationRuleMethod("HappyPathMockClass", null) + ] + TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) + + when: + def rule = new TransformationRule(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions) + def fhirResource = new FhirResourceMock(FhirBundleHelper.createMessageBundle(new HashMap())) + rule.runRule(fhirResource) + + then: + def messageHeaderStream = HapiTestHelper.resourceInBundle(fhirResource.getUnderlyingResource() as Bundle, MessageHeader.class) + def actualMessageHeader = messageHeaderStream.filter{resource -> resource.getEventCoding().getCode().equals("mock_code")}.findFirst().orElse(null) + actualMessageHeader.getEventCoding().getCode() == "mock_code" + } + + def "runRule() throws RuntimeException when an invalid class is given as input"() { + given: + def ruleName = "Rule name" + def ruleDescription = "Rule Description" + def ruleMessage = "Rule Warning Message" + def ruleConditions = ["condition1", "condition2"] + def ruleActions = [ + new TransformationRuleMethod("DoesNotCompute", null) + ] + TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) + + when: + def rule = new TransformationRule(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions) + def fhirResource = new FhirResourceMock(FhirBundleHelper.createMessageBundle(new HashMap())) + rule.runRule(fhirResource) + + then: + thrown(RuntimeException) + } + + def "runRule() throws NoSuchMethodException when given a class without transform"() { + given: + def ruleName = "Rule name" + def ruleDescription = "Rule Description" + def ruleMessage = "Rule Warning Message" + def ruleConditions = ["condition1", "condition2"] + def ruleActions = [ + new TransformationRuleMethod("NoSuchMethodExceptionMockClass", null) + ] + + TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) + + when: + def rule = new TransformationRule(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions) + def fhirResource = new FhirResourceMock(FhirBundleHelper.createMessageBundle(new HashMap())) + rule.runRule(fhirResource) + + then: + 1 * mockLogger.logError(_, _) + } + + def "runRule() throws InstantiationException when given abstract class input"() { + given: + def ruleName = "Rule name" + def ruleDescription = "Rule Description" + def ruleMessage = "Rule Warning Message" + def ruleConditions = ["condition1", "condition2"] + def ruleActions = [ + new TransformationRuleMethod("InstantiationExceptionMockClass", null) + ] + + TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) + + when: + def rule = new TransformationRule(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions) + def fhirResource = new FhirResourceMock(FhirBundleHelper.createMessageBundle(new HashMap())) + rule.runRule(fhirResource) + + then: + 1 * mockLogger.logError(_, _) + } + + def "runRule() throws IllegalAccessException when given a private constructor class input"() { + given: + def ruleName = "Rule name" + def ruleDescription = "Rule Description" + def ruleMessage = "Rule Warning Message" + def ruleConditions = ["condition1", "condition2"] + def ruleActions = [ + new TransformationRuleMethod("IllegalAccessExceptionMockClass", null) + ] + + TestApplicationContext.register(HapiFhir, Mock(HapiFhir)) + + when: + def rule = new TransformationRule(ruleName, ruleDescription, ruleMessage, ruleConditions, ruleActions) + def fhirResource = new FhirResourceMock(FhirBundleHelper.createMessageBundle(new HashMap())) + rule.runRule(fhirResource) + + then: + 1 * mockLogger.logError(_, _) } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/HappyPathMockClass.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/HappyPathMockClass.groovy new file mode 100644 index 000000000..561c69b55 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/HappyPathMockClass.groovy @@ -0,0 +1,24 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation +import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.MessageHeader + +class HappyPathMockClass implements CustomFhirTransformation { + + @Override + public void transform(final FhirResource resource, final Map args) { + Bundle bundle = (Bundle) resource.getUnderlyingResource() + MessageHeader messageHeader = new MessageHeader() + + String messageTypeCode = "mock_code" + Coding eventCoding = new Coding() + eventCoding.setCode(messageTypeCode) + eventCoding.setDisplay(String.format("mock^code^%s", messageTypeCode)) + eventCoding.setSystem("http://terminology.hl7.org/CodeSystem/v2-0003") + messageHeader.setEvent(eventCoding) + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/IllegalAccessExceptionMockClass.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/IllegalAccessExceptionMockClass.groovy new file mode 100644 index 000000000..a63173651 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/IllegalAccessExceptionMockClass.groovy @@ -0,0 +1,13 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource + +class IllegalAccessExceptionMockClass { + + private IllegalAccessExceptionMockClass() { + } + + public void transform(final FhirResource resource, final Map args) { + // empty for tests + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/InstantiationExceptionMockClass.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/InstantiationExceptionMockClass.groovy new file mode 100644 index 000000000..c6011c587 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/InstantiationExceptionMockClass.groovy @@ -0,0 +1,4 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom + +abstract class InstantiationExceptionMockClass { +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/NoSuchMethodExceptionMockClass.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/NoSuchMethodExceptionMockClass.groovy new file mode 100644 index 000000000..ed83f2f5a --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/NoSuchMethodExceptionMockClass.groovy @@ -0,0 +1,15 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource + +class NoSuchMethodExceptionMockClass { + private String imNotNull + + NoSuchMethodExceptionMockClass(String notNullConstructor) { + imNotNull = notNullConstructor + } + + public void noTransform(final FhirResource resource, final Map args) { + //No such method + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiTestHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiTestHelper.groovy index 176d3b542..dfb5ba9e8 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiTestHelper.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiTestHelper.groovy @@ -1,4 +1,12 @@ package gov.hhs.cdc.trustedintermediary.external.hapi +import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.Resource + +import java.util.stream.Stream + class HapiTestHelper { + static Stream resourceInBundle(Bundle bundle, Class resourceType) { + return HapiHelper.resourcesInBundle(bundle, resourceType) + } } From 5cee63ef62f06603dc34706aeabe5714628c2ae5 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Fri, 3 May 2024 14:42:54 -0500 Subject: [PATCH 123/151] Remove duplicate unit tests --- .../partner/PartnerMetadataOrchestratorTest.groovy | 11 ----------- .../DatabasePartnerMetadataStorageTest.groovy | 13 ------------- 2 files changed, 24 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy index aa9020126..324b302bf 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy @@ -283,17 +283,6 @@ class PartnerMetadataOrchestratorTest extends Specification { 1 * mockPartnerMetadataStorage.saveMetadata(updatedPartnerMetadata) } - def "updateMetadataForSentMessage test case when sentSubmissionId is null"() { - given: - def sentSubmissionId = null - - when: - PartnerMetadataOrchestrator.getInstance().updateMetadataForSentMessage(receivedSubmissionId, sentSubmissionId) - - then: - 0 * mockPartnerMetadataStorage.readMetadata(receivedSubmissionId) - } - def "getMetadata throws PartnerMetadataException on client error"() { given: def partnerMetadata = new PartnerMetadata(receivedSubmissionId, "sentSubmissionId", Instant.now(), null, "hash", PartnerMetadataStatus.PENDING, "failureReason", PartnerMetadataMessageType.RESULT, sendingApp, sendingFacility, receivingApp, receivingFacility, placerOrderNumber) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorageTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorageTest.groovy index 6243f0fc2..0f32a2e9d 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorageTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorageTest.groovy @@ -265,19 +265,6 @@ class DatabasePartnerMetadataStorageTest extends Specification { thrown(PartnerMetadataException) } - def "readMetadataForSender happy path works"() { - given: - def expectedResult = Set.of(mockMetadata) - - mockDao.fetchManyData(_ as Function, _ as Function, _) >> expectedResult - - when: - def actualResult = DatabasePartnerMetadataStorage.getInstance().readMetadataForSender("TestSender") - - then: - actualResult == expectedResult - } - def "readMetadataForSender unhappy path works"() { given: mockDao.fetchManyData(_ as Function, _ as Function, _) >> { throw new SQLException("Something went wrong!") } From 83b4ddb983eccc916f2b0f1bcb79d5a5d83708db Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Fri, 3 May 2024 14:43:41 -0500 Subject: [PATCH 124/151] Update HapiOrderConverterTest syntax --- .../external/hapi/HapiOrderConverterTest.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index 5e9f96b02..bc54a98a7 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -96,7 +96,8 @@ class HapiOrderConverterTest extends Specification { when: HapiOrderConverter.convertDemographicsToOrder(mockDemographicsResource) - def messageHeader = HapiHelper.resourcesInBundle(mockDemographicsResource, MessageHeader.class).findFirst().orElse(null) + def messageHeader = HapiHelper. + resourcesInBundle(mockDemographicsResource, MessageHeader.class).findFirst().orElse(null) then: messageHeader.hasId() From 9729b86f53b13684a8e12d9985659d6fdc68023e Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 13:03:33 -0700 Subject: [PATCH 125/151] Assert state before transformation in integration tests + add function to validate expected changes that will be used in other tests --- ...sformationRuleEngineIntegrationTest.groovy | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index ca47e67ab..05b34cedb 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,7 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation import gov.hhs.cdc.trustedintermediary.FhirBundleHelper -import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirImplementation @@ -14,8 +13,6 @@ import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.MessageHeader import org.hl7.fhir.r4.model.Patient -import org.hl7.fhir.r4.model.ServiceRequest -import org.hl7.fhir.r4.model.Provenance import spock.lang.Specification class TransformationRuleEngineIntegrationTest extends Specification { @@ -64,19 +61,18 @@ class TransformationRuleEngineIntegrationTest extends Specification { def ruleName = "addEtorProcessingTag" // we could also use this file for testing the rule: e2e/orders/001_OML_O21_short.fhir def bundle = new Bundle() - def untouchedBundle = bundle.copy() - engine.ensureRulesLoaded() def rule = engine.getRuleByName(ruleName) + expect: + FhirBundleHelper.resourceInBundle(bundle, MessageHeader) == null + when: rule.runRule(new HapiFhirResource(bundle)) - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) then: 0 * mockLogger.logError(_ as String, _ as Exception) - !bundle.equalsDeep(untouchedBundle) - messageHeader.meta.tag.last().code == "ETOR" + transformMethodAppliesExpectedChanges(ruleName, bundle) } def "test rule transformation accuracy: convertToOmlOrder"() { @@ -84,45 +80,40 @@ class TransformationRuleEngineIntegrationTest extends Specification { def ruleName = "convertToOmlOrder" // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "ORM_O01") - def untouchedBundle = bundle.copy() engine.ensureRulesLoaded() def rule = engine.getRuleByName(ruleName) + expect: + FhirBundleHelper.resourceInBundle(bundle, MessageHeader).event.code == "O01" + when: rule.runRule(new HapiFhirResource(bundle)) - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) - def untouchedMessageHeader = FhirBundleHelper.resourceInBundle(untouchedBundle, MessageHeader) then: 0 * mockLogger.logError(_ as String, _ as Exception) - !bundle.equalsDeep(untouchedBundle) - untouchedMessageHeader.event.code == "O01" - messageHeader.event.code == "O21" + transformMethodAppliesExpectedChanges(ruleName, bundle) } def "test rule transformation accuracy: addContactSectionToPatientResource"() { given: def ruleName = "addContactSectionToPatientResource" - // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "OML_O21") bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) - def untouchedBundle = bundle.copy() engine.ensureRulesLoaded() def rule = engine.getRuleByName(ruleName) + expect: + FhirBundleHelper.resourceInBundle(bundle, Patient).contact.isEmpty() + when: rule.runRule(new HapiFhirResource(bundle)) - def patient = FhirBundleHelper.resourceInBundle(bundle, Patient.class) - def untouchedPatient = FhirBundleHelper.resourceInBundle(untouchedBundle, Patient) then: 0 * mockLogger.logError(_ as String, _ as Exception) - !bundle.equalsDeep(untouchedBundle) - untouchedPatient.contact.isEmpty() - patient.contact.size() > 0 + transformMethodAppliesExpectedChanges(ruleName, bundle) } // todo: ignoring while figuring out how to filter the demographics example @@ -151,4 +142,24 @@ class TransformationRuleEngineIntegrationTest extends Specification { // serviceRequest != null // provenance != null // } + + boolean transformMethodAppliesExpectedChanges(String method, Bundle bundle) { + def expected = false + switch (method) { + case "addEtorProcessingTag": + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) + expected = messageHeader.meta.tag.last().code == "ETOR" + return expected + case "convertToOmlOrder": + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) + expected = messageHeader.event.code == "O21" + return expected + case "addContactSectionToPatientResource": + def patient = FhirBundleHelper.resourceInBundle(bundle, Patient.class) + expected = patient.contact.size() > 0 + return expected + default: + return false + } + } } From f511a5dae860a26ca310e8e6291d4727164fc199 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Fri, 3 May 2024 16:31:43 -0400 Subject: [PATCH 126/151] Update TransformationRuleEngine.java Update format to match ValidationRuleEngine --- .../TransformationRuleEngine.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 86f5973ba..939029025 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -6,7 +6,6 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -40,19 +39,14 @@ public void ensureRulesLoaded() throws RuleLoaderException { if (rules.isEmpty()) { synchronized (this) { if (rules.isEmpty()) { - String path = ruleDefinitionsFileName; - try (InputStream resourceStream = - getClass().getClassLoader().getResourceAsStream(path)) { - if (resourceStream == null) { - throw new RuleLoaderException( - "No resource found at " + path, new IOException()); - } - List parsedRules = - ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); - this.rules.addAll(parsedRules); - } catch (IOException e) { - throw new RuleLoaderException("Failed to load rules from " + path, e); - } + InputStream resourceStream = + getClass() + .getClassLoader() + .getResourceAsStream(ruleDefinitionsFileName); + assert resourceStream != null; + List parsedRules = + ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); + this.rules.addAll(parsedRules); } } } From 639ad9dafc303aa1c5dcbc1fca47828e46e0ed83 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 15:03:02 -0700 Subject: [PATCH 127/151] Refactored to use lookup table instead of switch statement --- ...sformationRuleEngineIntegrationTest.groovy | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 05b34cedb..0bbda3d4d 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -19,6 +19,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { def engine = TransformationRuleEngine.getInstance("transformation_definitions.json") def fhir = HapiFhirImplementation.getInstance() def mockLogger = Mock(Logger) + def transformationLookup def setup() { TestApplicationContext.reset() @@ -32,6 +33,21 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.injectRegisteredImplementations() + + transformationLookup = [ + "addEtorProcessingTag": { Bundle bundle -> + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) + messageHeader.meta.tag.last().code == "ETOR" + }, + "convertToOmlOrder": { Bundle bundle -> + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) + messageHeader.event.code == "O21" + }, + "addContactSectionToPatientResource": { Bundle bundle -> + def patient = FhirBundleHelper.resourceInBundle(bundle, Patient) + patient.contact.size() > 0 + } + ] } def "transformation rules run without error"() { @@ -72,7 +88,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logError(_ as String, _ as Exception) - transformMethodAppliesExpectedChanges(ruleName, bundle) + transformationLookup[ruleName](bundle) } def "test rule transformation accuracy: convertToOmlOrder"() { @@ -92,7 +108,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logError(_ as String, _ as Exception) - transformMethodAppliesExpectedChanges(ruleName, bundle) + transformationLookup[ruleName](bundle) } def "test rule transformation accuracy: addContactSectionToPatientResource"() { @@ -113,7 +129,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logError(_ as String, _ as Exception) - transformMethodAppliesExpectedChanges(ruleName, bundle) + transformationLookup[ruleName](bundle) } // todo: ignoring while figuring out how to filter the demographics example @@ -142,24 +158,4 @@ class TransformationRuleEngineIntegrationTest extends Specification { // serviceRequest != null // provenance != null // } - - boolean transformMethodAppliesExpectedChanges(String method, Bundle bundle) { - def expected = false - switch (method) { - case "addEtorProcessingTag": - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) - expected = messageHeader.meta.tag.last().code == "ETOR" - return expected - case "convertToOmlOrder": - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) - expected = messageHeader.event.code == "O21" - return expected - case "addContactSectionToPatientResource": - def patient = FhirBundleHelper.resourceInBundle(bundle, Patient.class) - expected = patient.contact.size() > 0 - return expected - default: - return false - } - } } From 3de164a6dcb3cb1bb3ea45ee97253d7e8ec5852a Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Fri, 3 May 2024 15:37:04 -0700 Subject: [PATCH 128/151] Added integration test to make sure consutive transformations don't interfere with each other. Can make more generic --- ...sformationRuleEngineIntegrationTest.groovy | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 0bbda3d4d..20219b1f1 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation +import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.FhirBundleHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader @@ -132,6 +133,33 @@ class TransformationRuleEngineIntegrationTest extends Specification { transformationLookup[ruleName](bundle) } + def "consecutively applied transformations don't interfere with each other: 003_2_ORM_O01_short_linked_to_002_ORU_R01_short"() { + given: + def testFile = "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" + def transformationsToApply = [ + "convertToOmlOrder", + "addContactSectionToPatientResource" + ] + def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) + def bundle = (Bundle) fhirResource.getUnderlyingResource() + + expect: + FhirBundleHelper.resourceInBundle(bundle, MessageHeader).event.code == "O01" + FhirBundleHelper.resourceInBundle(bundle, Patient).contact.isEmpty() + + when: + engine.ensureRulesLoaded() + transformationsToApply.each { ruleName -> + engine.getRuleByName(ruleName).runRule(fhirResource) + } + + then: + 0 * mockLogger.logError(_ as String, _ as Exception) + transformationsToApply.each { ruleName -> + transformationLookup[ruleName](bundle) + } + } + // todo: ignoring while figuring out how to filter the demographics example // def "test rule transformation accuracy: convertDemographicsToOrder"() { // given: From db3c44bb9aebc08a3acdf0da1904053411955195 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 6 May 2024 17:04:41 -0400 Subject: [PATCH 129/151] Basic implementation of sending facility value set --- .../addSendingFacilityToMessageHeader.java | 24 +++++++++++++++++++ .../external/hapi/HapiOrderConverter.java | 12 ++++++++++ .../resources/transformation_definitions.json | 14 +++++++++++ 3 files changed, 50 insertions(+) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java new file mode 100644 index 000000000..af81fe2c5 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java @@ -0,0 +1,24 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; + +import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; +import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; +import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; +import java.util.Map; +import org.hl7.fhir.r4.model.Bundle; + +public class addSendingFacilityToMessageHeader implements CustomFhirTransformation { + + private final MetricMetadata metadata = + ApplicationContext.getImplementation(MetricMetadata.class); + + @Override + public void transform(FhirResource resource, Map args) { + Bundle bundle = (Bundle) resource.getUnderlyingResource(); + HapiOrderConverter.addSendingFacilityToMessageHeader( + bundle, "CDPH"); // @todo use args to pass org name + metadata.put(bundle.getId(), EtorMetadataStep.CONTACT_SECTION_ADDED_TO_PATIENT); + } +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java index cd81f3399..753ae07d2 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java @@ -13,6 +13,7 @@ import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Provenance; import org.hl7.fhir.r4.model.Reference; @@ -157,4 +158,15 @@ public static Patient findPatientOrNull(Bundle bundle) { public static Stream findAllPatients(Bundle bundle) { return HapiHelper.resourcesInBundle(bundle, Patient.class); } + + public static void addSendingFacilityToMessageHeader(Bundle bundle, String name) { + var header = HapiHelper.resourcesInBundle(bundle, MessageHeader.class).findFirst(); + if (header.isEmpty()) { + return; + } + + var org = new Organization(); + org.setName(name); + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(org)); + } } diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 6fbbd07d2..aeb2bc055 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -38,6 +38,20 @@ "args": { } } ] + }, + { + "name": "addSendingFacilityToMessageHeader", + "description": "Add Contact Section to Patient Resource in OML Order", + "message": "", + "conditions": [ + "Bundle.entry.resource.ofType(MessageHeader).event.code = 'O21'" + ], + "rules": [ + { + "name": "addSendingFacilityToMessageHeader", + "args": { } + } + ] } ] } From ffd54ea7de2c27862861e9bf11783a3fd864610f Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Mon, 6 May 2024 17:05:17 -0400 Subject: [PATCH 130/151] Update TransformationRuleEngineIntegrationTest.groovy Adding spot for adding test for new rule --- .../TransformationRuleEngineIntegrationTest.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 20219b1f1..99194b7d8 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -133,6 +133,11 @@ class TransformationRuleEngineIntegrationTest extends Specification { transformationLookup[ruleName](bundle) } + // def "test rule transformation accuracy: addSendingFacilityToMessageHeader"() { + // given: + // def ruleName = "addSendingFacilityToMessageHeader" + // } + def "consecutively applied transformations don't interfere with each other: 003_2_ORM_O01_short_linked_to_002_ORU_R01_short"() { given: def testFile = "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" From f45701a6750250ba0de6b67a8a0131af13722b58 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Tue, 7 May 2024 11:08:52 -0400 Subject: [PATCH 131/151] Update the new rule --- .../custom/addSendingFacilityToMessageHeader.java | 2 +- .../src/main/resources/transformation_definitions.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java index af81fe2c5..80cad15dd 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java @@ -18,7 +18,7 @@ public class addSendingFacilityToMessageHeader implements CustomFhirTransformati public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); HapiOrderConverter.addSendingFacilityToMessageHeader( - bundle, "CDPH"); // @todo use args to pass org name + bundle, args.get("name")); // @todo use args to pass org name metadata.put(bundle.getId(), EtorMetadataStep.CONTACT_SECTION_ADDED_TO_PATIENT); } } diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index aeb2bc055..220736c68 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -41,15 +41,15 @@ }, { "name": "addSendingFacilityToMessageHeader", - "description": "Add Contact Section to Patient Resource in OML Order", + "description": "Add standard sending facility information", "message": "", - "conditions": [ - "Bundle.entry.resource.ofType(MessageHeader).event.code = 'O21'" - ], + "conditions": [ ], "rules": [ { "name": "addSendingFacilityToMessageHeader", - "args": { } + "args": { + "name": "CDPH" + } } ] } From 4b1a82597e9c655c30bf3fe8905adfd46be670b4 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Tue, 7 May 2024 11:09:10 -0400 Subject: [PATCH 132/151] Update TransformationRuleEngineIntegrationTest.groovy Updating sendingfacility test --- ...sformationRuleEngineIntegrationTest.groovy | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 99194b7d8..596a43927 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -13,6 +13,7 @@ import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.MessageHeader +import org.hl7.fhir.r4.model.Organization import org.hl7.fhir.r4.model.Patient import spock.lang.Specification @@ -36,17 +37,21 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.injectRegisteredImplementations() transformationLookup = [ - "addEtorProcessingTag": { Bundle bundle -> + "addEtorProcessingTag" : { Bundle bundle -> def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) messageHeader.meta.tag.last().code == "ETOR" }, - "convertToOmlOrder": { Bundle bundle -> + "convertToOmlOrder" : { Bundle bundle -> def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) messageHeader.event.code == "O21" }, "addContactSectionToPatientResource": { Bundle bundle -> def patient = FhirBundleHelper.resourceInBundle(bundle, Patient) patient.contact.size() > 0 + }, + "addSendingFacilityToMessageHeader": { Bundle bundle -> + def org = FhirBundleHelper.resourceInBundle(bundle, Organization) + org.name == "testName" } ] } @@ -133,10 +138,27 @@ class TransformationRuleEngineIntegrationTest extends Specification { transformationLookup[ruleName](bundle) } - // def "test rule transformation accuracy: addSendingFacilityToMessageHeader"() { - // given: - // def ruleName = "addSendingFacilityToMessageHeader" - // } + def "test rule transformation accuracy: addSendingFacilityToMessageHeader"() { + given: + def ruleName = "addSendingFacilityToMessageHeader" + // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir + def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "OML_O21") + // bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) + + engine.ensureRulesLoaded() + def rule = engine.getRuleByName(ruleName) + + expect: + FhirBundleHelper.resourceInBundle(bundle, Organization).isEmpty() + // org.name == "testName" + + when: + rule.runRule(new HapiFhirResource(bundle)) + + then: + 0 * mockLogger.logError(_ as String, _ as Exception) + transformationLookup[ruleName](bundle) + } def "consecutively applied transformations don't interfere with each other: 003_2_ORM_O01_short_linked_to_002_ORU_R01_short"() { given: From 46306b1680126abc6ff8459c28896f173d7c5c9f Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 08:57:37 -0700 Subject: [PATCH 133/151] Reverting adding lookup table since it obscures details when test fails --- ...sformationRuleEngineIntegrationTest.groovy | 66 ++++--------------- 1 file changed, 12 insertions(+), 54 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 596a43927..643d47503 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -21,7 +21,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { def engine = TransformationRuleEngine.getInstance("transformation_definitions.json") def fhir = HapiFhirImplementation.getInstance() def mockLogger = Mock(Logger) - def transformationLookup def setup() { TestApplicationContext.reset() @@ -35,25 +34,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.injectRegisteredImplementations() - - transformationLookup = [ - "addEtorProcessingTag" : { Bundle bundle -> - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) - messageHeader.meta.tag.last().code == "ETOR" - }, - "convertToOmlOrder" : { Bundle bundle -> - def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) - messageHeader.event.code == "O21" - }, - "addContactSectionToPatientResource": { Bundle bundle -> - def patient = FhirBundleHelper.resourceInBundle(bundle, Patient) - patient.contact.size() > 0 - }, - "addSendingFacilityToMessageHeader": { Bundle bundle -> - def org = FhirBundleHelper.resourceInBundle(bundle, Organization) - org.name == "testName" - } - ] } def "transformation rules run without error"() { @@ -91,10 +71,11 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: rule.runRule(new HapiFhirResource(bundle)) + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) then: 0 * mockLogger.logError(_ as String, _ as Exception) - transformationLookup[ruleName](bundle) + messageHeader.meta.tag.last().code == "ETOR" } def "test rule transformation accuracy: convertToOmlOrder"() { @@ -111,10 +92,11 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: rule.runRule(new HapiFhirResource(bundle)) + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) then: 0 * mockLogger.logError(_ as String, _ as Exception) - transformationLookup[ruleName](bundle) + messageHeader.event.code == "O21" } def "test rule transformation accuracy: addContactSectionToPatientResource"() { @@ -132,10 +114,11 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: rule.runRule(new HapiFhirResource(bundle)) + def patient = FhirBundleHelper.resourceInBundle(bundle, Patient) then: 0 * mockLogger.logError(_ as String, _ as Exception) - transformationLookup[ruleName](bundle) + patient.contact.size() > 0 } def "test rule transformation accuracy: addSendingFacilityToMessageHeader"() { @@ -154,10 +137,11 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: rule.runRule(new HapiFhirResource(bundle)) + def org = FhirBundleHelper.resourceInBundle(bundle, Organization) then: 0 * mockLogger.logError(_ as String, _ as Exception) - transformationLookup[ruleName](bundle) + org.name == "testName" } def "consecutively applied transformations don't interfere with each other: 003_2_ORM_O01_short_linked_to_002_ORU_R01_short"() { @@ -179,38 +163,12 @@ class TransformationRuleEngineIntegrationTest extends Specification { transformationsToApply.each { ruleName -> engine.getRuleByName(ruleName).runRule(fhirResource) } + def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) + def patient = FhirBundleHelper.resourceInBundle(bundle, Patient) then: 0 * mockLogger.logError(_ as String, _ as Exception) - transformationsToApply.each { ruleName -> - transformationLookup[ruleName](bundle) - } + messageHeader.event.code == "O21" + patient.contact.size() > 0 } - - // todo: ignoring while figuring out how to filter the demographics example - // def "test rule transformation accuracy: convertDemographicsToOrder"() { - // given: - // def ruleName = "convertDemographicsToOrder" - // def testFile = "e2e/demographics/001_Patient_NBS.fhir" - // - // def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) - // def bundle = (Bundle) fhirResource.getUnderlyingResource() - // def untouchedBundle = bundle.copy() - // - // engine.ensureRulesLoaded() - // def rule = engine.getRuleByName(ruleName) - // - // when: - // rule.runRule(fhirResource) - // def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) - // def serviceRequest = FhirBundleHelper.resourceInBundle(bundle, ServiceRequest) - // def provenance = FhirBundleHelper.resourceInBundle(bundle, Provenance) - // - // then: - // 0 * mockLogger.logError(_ as String, _ as Exception) - // !bundle.equalsDeep(untouchedBundle) - // messageHeader != null - // serviceRequest != null - // provenance != null - // } } From d567bf2c3f361ebe23b4b47b407b34bfbe2159f9 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 10:18:18 -0700 Subject: [PATCH 134/151] Moving code for addSendingFacilityToMessageHeader to new branch --- .../addSendingFacilityToMessageHeader.java | 24 --------- .../external/hapi/HapiOrderConverter.java | 12 ----- .../resources/transformation_definitions.json | 14 ----- ...sformationRuleEngineIntegrationTest.groovy | 52 ++++++------------- 4 files changed, 15 insertions(+), 87 deletions(-) delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java deleted file mode 100644 index 80cad15dd..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addSendingFacilityToMessageHeader.java +++ /dev/null @@ -1,24 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; - -import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; -import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; -import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; -import java.util.Map; -import org.hl7.fhir.r4.model.Bundle; - -public class addSendingFacilityToMessageHeader implements CustomFhirTransformation { - - private final MetricMetadata metadata = - ApplicationContext.getImplementation(MetricMetadata.class); - - @Override - public void transform(FhirResource resource, Map args) { - Bundle bundle = (Bundle) resource.getUnderlyingResource(); - HapiOrderConverter.addSendingFacilityToMessageHeader( - bundle, args.get("name")); // @todo use args to pass org name - metadata.put(bundle.getId(), EtorMetadataStep.CONTACT_SECTION_ADDED_TO_PATIENT); - } -} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java index 753ae07d2..cd81f3399 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java @@ -13,7 +13,6 @@ import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Meta; -import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Provenance; import org.hl7.fhir.r4.model.Reference; @@ -158,15 +157,4 @@ public static Patient findPatientOrNull(Bundle bundle) { public static Stream findAllPatients(Bundle bundle) { return HapiHelper.resourcesInBundle(bundle, Patient.class); } - - public static void addSendingFacilityToMessageHeader(Bundle bundle, String name) { - var header = HapiHelper.resourcesInBundle(bundle, MessageHeader.class).findFirst(); - if (header.isEmpty()) { - return; - } - - var org = new Organization(); - org.setName(name); - bundle.addEntry(new Bundle.BundleEntryComponent().setResource(org)); - } } diff --git a/etor/src/main/resources/transformation_definitions.json b/etor/src/main/resources/transformation_definitions.json index 220736c68..6fbbd07d2 100644 --- a/etor/src/main/resources/transformation_definitions.json +++ b/etor/src/main/resources/transformation_definitions.json @@ -38,20 +38,6 @@ "args": { } } ] - }, - { - "name": "addSendingFacilityToMessageHeader", - "description": "Add standard sending facility information", - "message": "", - "conditions": [ ], - "rules": [ - { - "name": "addSendingFacilityToMessageHeader", - "args": { - "name": "CDPH" - } - } - ] } ] } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 643d47503..1d669368f 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -18,7 +18,8 @@ import org.hl7.fhir.r4.model.Patient import spock.lang.Specification class TransformationRuleEngineIntegrationTest extends Specification { - def engine = TransformationRuleEngine.getInstance("transformation_definitions.json") + + def engine = TransformationRuleEngine.getInstance('transformation_definitions.json') def fhir = HapiFhirImplementation.getInstance() def mockLogger = Mock(Logger) @@ -60,7 +61,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { def "test rule transformation accuracy: addEtorProcessingTag"() { given: - def ruleName = "addEtorProcessingTag" + def ruleName = 'addEtorProcessingTag' // we could also use this file for testing the rule: e2e/orders/001_OML_O21_short.fhir def bundle = new Bundle() engine.ensureRulesLoaded() @@ -75,20 +76,20 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logError(_ as String, _ as Exception) - messageHeader.meta.tag.last().code == "ETOR" + messageHeader.meta.tag.last().code == 'ETOR' } def "test rule transformation accuracy: convertToOmlOrder"() { given: - def ruleName = "convertToOmlOrder" + def ruleName = 'convertToOmlOrder' // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir - def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "ORM_O01") + def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: 'ORM_O01') engine.ensureRulesLoaded() def rule = engine.getRuleByName(ruleName) expect: - FhirBundleHelper.resourceInBundle(bundle, MessageHeader).event.code == "O01" + FhirBundleHelper.resourceInBundle(bundle, MessageHeader).event.code == 'O01' when: rule.runRule(new HapiFhirResource(bundle)) @@ -96,14 +97,14 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logError(_ as String, _ as Exception) - messageHeader.event.code == "O21" + messageHeader.event.code == 'O21' } def "test rule transformation accuracy: addContactSectionToPatientResource"() { given: - def ruleName = "addContactSectionToPatientResource" + def ruleName = 'addContactSectionToPatientResource' // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir - def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "OML_O21") + def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: 'OML_O21') bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) engine.ensureRulesLoaded() @@ -121,41 +122,18 @@ class TransformationRuleEngineIntegrationTest extends Specification { patient.contact.size() > 0 } - def "test rule transformation accuracy: addSendingFacilityToMessageHeader"() { - given: - def ruleName = "addSendingFacilityToMessageHeader" - // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir - def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: "OML_O21") - // bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) - - engine.ensureRulesLoaded() - def rule = engine.getRuleByName(ruleName) - - expect: - FhirBundleHelper.resourceInBundle(bundle, Organization).isEmpty() - // org.name == "testName" - - when: - rule.runRule(new HapiFhirResource(bundle)) - def org = FhirBundleHelper.resourceInBundle(bundle, Organization) - - then: - 0 * mockLogger.logError(_ as String, _ as Exception) - org.name == "testName" - } - def "consecutively applied transformations don't interfere with each other: 003_2_ORM_O01_short_linked_to_002_ORU_R01_short"() { given: - def testFile = "e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir" + def testFile = 'e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir' def transformationsToApply = [ - "convertToOmlOrder", - "addContactSectionToPatientResource" + 'convertToOmlOrder', + 'addContactSectionToPatientResource' ] def fhirResource = ExamplesHelper.getExampleFhirResource(testFile) def bundle = (Bundle) fhirResource.getUnderlyingResource() expect: - FhirBundleHelper.resourceInBundle(bundle, MessageHeader).event.code == "O01" + FhirBundleHelper.resourceInBundle(bundle, MessageHeader).event.code == 'O01' FhirBundleHelper.resourceInBundle(bundle, Patient).contact.isEmpty() when: @@ -168,7 +146,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: 0 * mockLogger.logError(_ as String, _ as Exception) - messageHeader.event.code == "O21" + messageHeader.event.code == 'O21' patient.contact.size() > 0 } } From 32e7e397a506a2e99cd704dbb23659d116ec5db8 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 10:52:06 -0700 Subject: [PATCH 135/151] Removed getRuleByName only used in tests and added helper method instead --- .../etor/ruleengine/RuleEngine.java | 2 -- .../transformation/TransformationRuleEngine.java | 8 -------- .../validation/ValidationRuleEngine.java | 8 -------- .../trustedintermediary/FhirBundleHelper.groovy | 1 - .../etor/ruleengine/RuleEngineHelper.groovy | 11 +++++++++++ ...TransformationRuleEngineIntegrationTest.groovy | 11 ++++++----- .../TransformationRuleEngineTest.groovy | 15 --------------- .../validation/ValidationRuleEngineTest.groovy | 15 --------------- 8 files changed, 17 insertions(+), 54 deletions(-) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineHelper.groovy diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 1fc855d07..6357598d8 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -10,6 +10,4 @@ public interface RuleEngine { void ensureRulesLoaded() throws RuleLoaderException; void runRules(FhirResource resource); - - Rule getRuleByName(String ruleName); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 939029025..91d79c3b4 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -68,12 +68,4 @@ public void runRules(FhirResource resource) { } }); } - - @Override - public TransformationRule getRuleByName(String ruleName) { - return rules.stream() - .filter(rule -> rule.getName().equals(ruleName)) - .findFirst() - .orElse(null); - } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java index f1dbbc931..55098c566 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java @@ -66,12 +66,4 @@ public void runRules(FhirResource resource) { } } } - - @Override - public ValidationRule getRuleByName(String ruleName) { - return rules.stream() - .filter(rule -> rule.getName().equals(ruleName)) - .findFirst() - .orElse(null); - } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy index bdfea3e55..6c2245bad 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirBundleHelper.groovy @@ -5,7 +5,6 @@ import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.MessageHeader import org.hl7.fhir.r4.model.Organization import org.hl7.fhir.r4.model.Reference -import org.hl7.fhir.r4.model.Resource class FhirBundleHelper { diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineHelper.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineHelper.groovy new file mode 100644 index 000000000..601ab3cb1 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngineHelper.groovy @@ -0,0 +1,11 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine + +class RuleEngineHelper { + + static T getRuleByName(List rules, String ruleName) { + return rules.stream() + .filter({ rule -> rule.getName() == ruleName }) + .findFirst() + .orElse(null) + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index 1d669368f..d022d3c52 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngineHelper import gov.hhs.cdc.trustedintermediary.ExamplesHelper import gov.hhs.cdc.trustedintermediary.FhirBundleHelper import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext @@ -13,7 +14,6 @@ import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.MessageHeader -import org.hl7.fhir.r4.model.Organization import org.hl7.fhir.r4.model.Patient import spock.lang.Specification @@ -65,7 +65,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { // we could also use this file for testing the rule: e2e/orders/001_OML_O21_short.fhir def bundle = new Bundle() engine.ensureRulesLoaded() - def rule = engine.getRuleByName(ruleName) + def rule = RuleEngineHelper.getRuleByName(engine.rules, ruleName) expect: FhirBundleHelper.resourceInBundle(bundle, MessageHeader) == null @@ -86,7 +86,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: 'ORM_O01') engine.ensureRulesLoaded() - def rule = engine.getRuleByName(ruleName) + def rule = RuleEngineHelper.getRuleByName(engine.rules, ruleName) expect: FhirBundleHelper.resourceInBundle(bundle, MessageHeader).event.code == 'O01' @@ -108,7 +108,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) engine.ensureRulesLoaded() - def rule = engine.getRuleByName(ruleName) + def rule = RuleEngineHelper.getRuleByName(engine.rules, ruleName) expect: FhirBundleHelper.resourceInBundle(bundle, Patient).contact.isEmpty() @@ -139,7 +139,8 @@ class TransformationRuleEngineIntegrationTest extends Specification { when: engine.ensureRulesLoaded() transformationsToApply.each { ruleName -> - engine.getRuleByName(ruleName).runRule(fhirResource) + def rule = RuleEngineHelper.getRuleByName(engine.rules, ruleName) + rule.runRule(fhirResource) } def messageHeader = FhirBundleHelper.resourceInBundle(bundle, MessageHeader) def patient = FhirBundleHelper.resourceInBundle(bundle, Patient) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy index 5e4468b4d..a42d3182d 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy @@ -134,19 +134,4 @@ class TransformationRuleEngineTest extends Specification { then: 1 * mockLogger.logError(_ as String, exception) } - - def "getRuleByName returns the rule with the given name"() { - given: - def ruleName = "Rule name" - def testRule = Mock(TransformationRule) - testRule.getName() >> ruleName - mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [testRule] - - when: - ruleEngine.ensureRulesLoaded() - def rule = ruleEngine.getRuleByName(ruleName) - - then: - rule == testRule - } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy index 8caa9a168..de29c400a 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy @@ -135,19 +135,4 @@ class ValidationRuleEngineTest extends Specification { then: 1 * mockLogger.logError(_ as String, exception) } - - def "getRuleByName returns the rule with the given name"() { - given: - def ruleName = "Rule name" - def testRule = Mock(ValidationRule) - testRule.getName() >> ruleName - mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) >> [testRule] - - when: - ruleEngine.ensureRulesLoaded() - def rule = ruleEngine.getRuleByName(ruleName) - - then: - rule == testRule - } } From 49709ef316ce69475ab3a5a3ecffd68f9badb401 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 11:09:35 -0700 Subject: [PATCH 136/151] Added javadocs --- .../ruleengine/transformation/CustomFhirTransformation.java | 4 ++++ .../custom/addContactSectionToPatientResource.java | 1 + .../transformation/custom/addEtorProcessingTag.java | 1 + .../transformation/custom/convertDemographicsToOrder.java | 4 ++++ .../ruleengine/transformation/custom/convertToOmlOrder.java | 1 + 5 files changed, 11 insertions(+) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java index 8aa178d62..74792534c 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java @@ -3,6 +3,10 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import java.util.Map; +/** + * Represents a custom transformation that can be applied to a FHIR resource. This interface is + * implemented by classes in the custom/ folder. + */ public interface CustomFhirTransformation { void transform(FhirResource resource, Map args); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java index 3f55e99ad..ae1d67880 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java @@ -9,6 +9,7 @@ import java.util.Map; import org.hl7.fhir.r4.model.Bundle; +/** Custom transformation to add a contact section to a patient resource. */ public class addContactSectionToPatientResource implements CustomFhirTransformation { private final MetricMetadata metadata = diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java index dd503d356..f01087a8a 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addEtorProcessingTag.java @@ -9,6 +9,7 @@ import java.util.Map; import org.hl7.fhir.r4.model.Bundle; +/** Custom transformation to add an ETOR tag to the message header of a FHIR bundle. */ public class addEtorProcessingTag implements CustomFhirTransformation { private final MetricMetadata metadata = diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java index 43288369d..6185b3d18 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java @@ -6,6 +6,10 @@ import java.util.Map; import org.hl7.fhir.r4.model.Bundle; +/** + * Custom transformation to convert a demographics message to an order. This class may be removed if + * we don't need to handle demographics. + */ public class convertDemographicsToOrder implements CustomFhirTransformation { @Override diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java index e5ce1d615..e59281844 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java @@ -9,6 +9,7 @@ import java.util.Map; import org.hl7.fhir.r4.model.Bundle; +/** Custom transformation to convert an ORM order to OML. */ public class convertToOmlOrder implements CustomFhirTransformation { private final MetricMetadata metadata = From 0bfdb89c500b85bd83e2e6f811842b81d668ca2d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 11:13:05 -0700 Subject: [PATCH 137/151] Added more javadocs --- .../ruleengine/transformation/TransformationRuleMethod.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleMethod.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleMethod.java index d93211c6b..4d0b2e10d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleMethod.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleMethod.java @@ -2,4 +2,10 @@ import java.util.Map; +/** + * Represents a transformation rule method. + * + * @param name The name of the transformation. + * @param args The arguments to pass to the transformation method. + */ public record TransformationRuleMethod(String name, Map args) {} From 461aa8df5343e2b2511d0c1c15d908fa69f39bb9 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 11:38:24 -0700 Subject: [PATCH 138/151] Created RuleException and bubbled it up to interrupt flow instead of just logging error --- .../etor/orders/SendOrderUseCase.java | 7 ++++++- .../etor/ruleengine/RuleEngine.java | 2 +- .../etor/ruleengine/RuleException.java | 7 +++++++ .../transformation/TransformationRule.java | 12 ++++++++---- .../transformation/TransformationRuleEngine.java | 14 +++++++------- 5 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleException.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java index edf3ed746..f889cdf2e 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java @@ -5,6 +5,7 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleException; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; @@ -42,7 +43,11 @@ public void convertAndSend(final Order order, String receivedSubmissionId) sendMessageHelper.savePartnerMetadataForReceivedMessage(partnerMetadata); - transformationEngine.runRules(order); + try { + transformationEngine.runRules(order); + } catch (RuleException e) { + throw new UnableToSendMessageException("Error running transformation rules", e); + } String outboundReportId = sender.send(order).orElse(null); logger.logInfo("Sent order reportId: {}", outboundReportId); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 6357598d8..372cf7bcb 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -9,5 +9,5 @@ public interface RuleEngine { void ensureRulesLoaded() throws RuleLoaderException; - void runRules(FhirResource resource); + void runRules(FhirResource resource) throws RuleException; } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleException.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleException.java new file mode 100644 index 000000000..6a4bc4268 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleException.java @@ -0,0 +1,7 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine; + +public class RuleException extends Exception { + public RuleException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 184997f25..07834b15a 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -2,6 +2,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleException; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; @@ -34,12 +35,15 @@ public TransformationRule( } @Override - public void runRule(FhirResource resource) { - this.getRules().forEach((transformation -> applyTransformation(transformation, resource))); + public void runRule(FhirResource resource) throws RuleException { + for (TransformationRuleMethod transformation : this.getRules()) { + applyTransformation(transformation, resource); + } } private void applyTransformation( - TransformationRuleMethod transformation, FhirResource resource) { + TransformationRuleMethod transformation, FhirResource resource) + throws RuleException { String name = transformation.name(); Map args = transformation.args(); logger.logInfo("Applying transformation: ", name); @@ -51,7 +55,7 @@ private void applyTransformation( | IllegalAccessException | InvocationTargetException | InstantiationException e) { - logger.logError("Error invoking method: " + name + ", due to: " + e.getMessage(), e); + throw new RuleException("Error invoking method: " + name, e); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 91d79c3b4..13a7b6123 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -2,6 +2,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleException; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; @@ -53,7 +54,7 @@ public void ensureRulesLoaded() throws RuleLoaderException { } @Override - public void runRules(FhirResource resource) { + public void runRules(FhirResource resource) throws RuleException { try { ensureRulesLoaded(); } catch (RuleLoaderException e) { @@ -61,11 +62,10 @@ public void runRules(FhirResource resource) { return; } - rules.forEach( - rule -> { - if (rule.shouldRun(resource)) { - rule.runRule((resource)); - } - }); + for (TransformationRule rule : rules) { + if (rule.shouldRun(resource)) { + rule.runRule((resource)); + } + } } } From 74d2c03f2dbf0496805c9fb6654c3acd29bb5c83 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 11:44:54 -0700 Subject: [PATCH 139/151] Renamed RuleException to TransformationRuleException and added missing try/catch --- .../demographics/ConvertAndSendDemographicsUsecase.java | 7 ++++++- .../trustedintermediary/etor/orders/SendOrderUseCase.java | 4 ++-- .../etor/results/SendResultUseCase.java | 7 ++++++- .../trustedintermediary/etor/ruleengine/RuleEngine.java | 2 +- .../trustedintermediary/etor/ruleengine/RuleException.java | 7 ------- .../etor/ruleengine/transformation/TransformationRule.java | 7 +++---- .../transformation/TransformationRuleEngine.java | 3 +-- .../transformation/TransformationRuleException.java | 7 +++++++ 8 files changed, 26 insertions(+), 18 deletions(-) delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleException.java create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleException.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java index 43dceb0b4..210d6141d 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java @@ -3,6 +3,7 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleException; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrder; import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; @@ -27,7 +28,11 @@ public static ConvertAndSendDemographicsUsecase getInstance() { private ConvertAndSendDemographicsUsecase() {} public void convertAndSend(Demographics demographics) throws UnableToSendMessageException { - transformationEngine.runRules(demographics); + try { + transformationEngine.runRules(demographics); + } catch (TransformationRuleException e) { + throw new UnableToSendMessageException("Error running transformation rules", e); + } var order = new HapiOrder((Bundle) demographics.getUnderlyingResource()); sender.send(order); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java index f889cdf2e..65044bc01 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java @@ -5,8 +5,8 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleException; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import javax.inject.Inject; @@ -45,7 +45,7 @@ public void convertAndSend(final Order order, String receivedSubmissionId) try { transformationEngine.runRules(order); - } catch (RuleException e) { + } catch (TransformationRuleException e) { throw new UnableToSendMessageException("Error running transformation rules", e); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java index 7f10d391d..90e95c6d6 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java @@ -6,6 +6,7 @@ import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import javax.inject.Inject; @@ -43,7 +44,11 @@ public void convertAndSend(Result result, String receivedSubmissionId) sendMessageHelper.savePartnerMetadataForReceivedMessage(partnerMetadata); - transformationEngine.runRules(result); + try { + transformationEngine.runRules(result); + } catch (TransformationRuleException e) { + throw new UnableToSendMessageException("Error running transformation rules", e); + } String outboundReportId = sender.send(result).orElse(null); logger.logInfo("Sent result reportId: {}", outboundReportId); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 372cf7bcb..6357598d8 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -9,5 +9,5 @@ public interface RuleEngine { void ensureRulesLoaded() throws RuleLoaderException; - void runRules(FhirResource resource) throws RuleException; + void runRules(FhirResource resource); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleException.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleException.java deleted file mode 100644 index 6a4bc4268..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleException.java +++ /dev/null @@ -1,7 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine; - -public class RuleException extends Exception { - public RuleException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 07834b15a..b98b3134e 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -2,7 +2,6 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleException; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; @@ -35,7 +34,7 @@ public TransformationRule( } @Override - public void runRule(FhirResource resource) throws RuleException { + public void runRule(FhirResource resource) throws TransformationRuleException { for (TransformationRuleMethod transformation : this.getRules()) { applyTransformation(transformation, resource); } @@ -43,7 +42,7 @@ public void runRule(FhirResource resource) throws RuleException { private void applyTransformation( TransformationRuleMethod transformation, FhirResource resource) - throws RuleException { + throws TransformationRuleException { String name = transformation.name(); Map args = transformation.args(); logger.logInfo("Applying transformation: ", name); @@ -55,7 +54,7 @@ private void applyTransformation( | IllegalAccessException | InvocationTargetException | InstantiationException e) { - throw new RuleException("Error invoking method: " + name, e); + throw new TransformationRuleException("Error invoking method: " + name, e); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 13a7b6123..0b43b75e7 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -2,7 +2,6 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleException; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; @@ -54,7 +53,7 @@ public void ensureRulesLoaded() throws RuleLoaderException { } @Override - public void runRules(FhirResource resource) throws RuleException { + public void runRules(FhirResource resource) throws TransformationRuleException { try { ensureRulesLoaded(); } catch (RuleLoaderException e) { diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleException.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleException.java new file mode 100644 index 000000000..e1af307c0 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleException.java @@ -0,0 +1,7 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; + +public class TransformationRuleException extends Exception { + public TransformationRuleException(String message, Throwable cause) { + super(message, cause); + } +} From 81364d8877e76e34562fb277bfcd6171415afa86 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 11:55:10 -0700 Subject: [PATCH 140/151] Renamed TransformationRuleException to RuleExecutionException --- .../demographics/ConvertAndSendDemographicsUsecase.java | 4 ++-- .../trustedintermediary/etor/orders/SendOrderUseCase.java | 4 ++-- .../etor/results/SendResultUseCase.java | 4 ++-- .../hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java | 2 +- .../etor/ruleengine/RuleExecutionException.java | 7 +++++++ .../etor/ruleengine/transformation/TransformationRule.java | 7 ++++--- .../transformation/TransformationRuleEngine.java | 3 ++- .../transformation/TransformationRuleException.java | 7 ------- 8 files changed, 20 insertions(+), 18 deletions(-) create mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleExecutionException.java delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleException.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java index 210d6141d..4b99aecfa 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecase.java @@ -2,8 +2,8 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleException; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrder; import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; @@ -30,7 +30,7 @@ private ConvertAndSendDemographicsUsecase() {} public void convertAndSend(Demographics demographics) throws UnableToSendMessageException { try { transformationEngine.runRules(demographics); - } catch (TransformationRuleException e) { + } catch (RuleExecutionException e) { throw new UnableToSendMessageException("Error running transformation rules", e); } var order = new HapiOrder((Bundle) demographics.getUnderlyingResource()); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java index 65044bc01..a342eb9aa 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCase.java @@ -5,8 +5,8 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import javax.inject.Inject; @@ -45,7 +45,7 @@ public void convertAndSend(final Order order, String receivedSubmissionId) try { transformationEngine.runRules(order); - } catch (TransformationRuleException e) { + } catch (RuleExecutionException e) { throw new UnableToSendMessageException("Error running transformation rules", e); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java index 90e95c6d6..f8f3dccaa 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCase.java @@ -5,8 +5,8 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata; import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import javax.inject.Inject; @@ -46,7 +46,7 @@ public void convertAndSend(Result result, String receivedSubmissionId) try { transformationEngine.runRules(result); - } catch (TransformationRuleException e) { + } catch (RuleExecutionException e) { throw new UnableToSendMessageException("Error running transformation rules", e); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java index 58453bb64..0a9ab78ff 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java @@ -79,7 +79,7 @@ public boolean shouldRun(FhirResource resource) { }); } - public void runRule(FhirResource resource) { + public void runRule(FhirResource resource) throws RuleExecutionException { throw new UnsupportedOperationException("This method must be implemented by subclasses."); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleExecutionException.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleExecutionException.java new file mode 100644 index 000000000..4a88f1bb7 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleExecutionException.java @@ -0,0 +1,7 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine; + +public class RuleExecutionException extends Exception { + public RuleExecutionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index b98b3134e..808eaa6f5 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -2,6 +2,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.Rule; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; @@ -34,7 +35,7 @@ public TransformationRule( } @Override - public void runRule(FhirResource resource) throws TransformationRuleException { + public void runRule(FhirResource resource) throws RuleExecutionException { for (TransformationRuleMethod transformation : this.getRules()) { applyTransformation(transformation, resource); } @@ -42,7 +43,7 @@ public void runRule(FhirResource resource) throws TransformationRuleException private void applyTransformation( TransformationRuleMethod transformation, FhirResource resource) - throws TransformationRuleException { + throws RuleExecutionException { String name = transformation.name(); Map args = transformation.args(); logger.logInfo("Applying transformation: ", name); @@ -54,7 +55,7 @@ private void applyTransformation( | IllegalAccessException | InvocationTargetException | InstantiationException e) { - throw new TransformationRuleException("Error invoking method: " + name, e); + throw new RuleExecutionException("Error invoking method: " + name, e); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index 0b43b75e7..c79312cc9 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -2,6 +2,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoader; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; @@ -53,7 +54,7 @@ public void ensureRulesLoaded() throws RuleLoaderException { } @Override - public void runRules(FhirResource resource) throws TransformationRuleException { + public void runRules(FhirResource resource) throws RuleExecutionException { try { ensureRulesLoaded(); } catch (RuleLoaderException e) { diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleException.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleException.java deleted file mode 100644 index e1af307c0..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleException.java +++ /dev/null @@ -1,7 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; - -public class TransformationRuleException extends Exception { - public TransformationRuleException(String message, Throwable cause) { - super(message, cause); - } -} From d1ec9abb9d72bfea1126df55a8bf7e8f8af9a00d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 12:00:56 -0700 Subject: [PATCH 141/151] Fixed tests --- .../etor/ruleengine/RuleEngine.java | 2 +- .../transformation/TransformationRuleTest.groovy | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java index 6357598d8..b45e6e334 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java @@ -9,5 +9,5 @@ public interface RuleEngine { void ensureRulesLoaded() throws RuleLoaderException; - void runRules(FhirResource resource); + void runRules(FhirResource resource) throws RuleExecutionException; } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy index aab55b357..e00f4ea4a 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException import gov.hhs.cdc.trustedintermediary.FhirBundleHelper import gov.hhs.cdc.trustedintermediary.FhirResourceMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext @@ -86,7 +87,7 @@ class TransformationRuleTest extends Specification { thrown(RuntimeException) } - def "runRule() throws NoSuchMethodException when given a class without transform"() { + def "runRule() throws RuleExecutionException when given a class without transform"() { given: def ruleName = "Rule name" def ruleDescription = "Rule Description" @@ -104,10 +105,10 @@ class TransformationRuleTest extends Specification { rule.runRule(fhirResource) then: - 1 * mockLogger.logError(_, _) + thrown(RuleExecutionException) } - def "runRule() throws InstantiationException when given abstract class input"() { + def "runRule() throws RuleExecutionException when given abstract class input"() { given: def ruleName = "Rule name" def ruleDescription = "Rule Description" @@ -125,10 +126,10 @@ class TransformationRuleTest extends Specification { rule.runRule(fhirResource) then: - 1 * mockLogger.logError(_, _) + thrown(RuleExecutionException) } - def "runRule() throws IllegalAccessException when given a private constructor class input"() { + def "runRule() throws RuleExecutionException when given a private constructor class input"() { given: def ruleName = "Rule name" def ruleDescription = "Rule Description" @@ -146,6 +147,6 @@ class TransformationRuleTest extends Specification { rule.runRule(fhirResource) then: - 1 * mockLogger.logError(_, _) + thrown(RuleExecutionException) } } From 9b8243a329e023d4fe48f3c0167fe882faa61c7f Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 12:59:14 -0700 Subject: [PATCH 142/151] Refactored to cache transformation class instantiation --- .../CustomFhirTransformation.java | 4 +- .../transformation/TransformationRule.java | 46 ++++++++----------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java index 74792534c..8d410d55a 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/CustomFhirTransformation.java @@ -1,6 +1,7 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException; import java.util.Map; /** @@ -8,5 +9,6 @@ * implemented by classes in the custom/ folder. */ public interface CustomFhirTransformation { - void transform(FhirResource resource, Map args); + void transform(FhirResource resource, Map args) + throws RuleExecutionException; } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java index 808eaa6f5..5c987f668 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRule.java @@ -17,7 +17,9 @@ */ public class TransformationRule extends Rule { - private static final Map> classCache = new ConcurrentHashMap<>(); + // private static final Map> classCache = new ConcurrentHashMap<>(); + private static final Map transformationInstanceCache = + new ConcurrentHashMap<>(); /** * Do not delete this constructor! It is used for JSON deserialization when loading rules from a @@ -48,27 +50,28 @@ private void applyTransformation( Map args = transformation.args(); logger.logInfo("Applying transformation: ", name); - try { - Class clazz = loadClassFromCache(name); - executeCustomTransformationMethod(clazz, resource, args); - } catch (NoSuchMethodException - | IllegalAccessException - | InvocationTargetException - | InstantiationException e) { - throw new RuleExecutionException("Error invoking method: " + name, e); - } + CustomFhirTransformation transformationInstance = getTransformationInstance(name); + transformationInstance.transform(resource, args); } - static Class loadClassFromCache(String className) throws RuntimeException { - return classCache.computeIfAbsent(className, TransformationRule::loadClassByName); + static CustomFhirTransformation getTransformationInstance(String name) throws RuntimeException { + return transformationInstanceCache.computeIfAbsent( + name, TransformationRule::createTransformationInstance); } - private static Class loadClassByName(String className) { - String fullClassName = getFullClassName(className); + private static CustomFhirTransformation createTransformationInstance( + String transformationName) { + String fullClassName = getFullClassName(transformationName); try { - return Class.forName(fullClassName); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); + Class clazz = Class.forName(fullClassName); + return (CustomFhirTransformation) clazz.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException + | InstantiationException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException e) { + throw new RuntimeException( + "Error creating transformation instance for: " + transformationName, e); } } @@ -77,13 +80,4 @@ private static String getFullClassName(String className) { "gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom"; return packageName + "." + className; } - - static void executeCustomTransformationMethod( - Class clazz, FhirResource resource, Map args) - throws NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { - CustomFhirTransformation transformation = - (CustomFhirTransformation) clazz.getDeclaredConstructor().newInstance(); - transformation.transform(resource, args); - } } From ec714d8312cde039e6cc294e6c3580f6975768e2 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 13:35:57 -0700 Subject: [PATCH 143/151] Fixed tests --- .../transformation/TransformationRuleTest.groovy | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy index e00f4ea4a..95e5f7e0f 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleTest.groovy @@ -1,6 +1,5 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException import gov.hhs.cdc.trustedintermediary.FhirBundleHelper import gov.hhs.cdc.trustedintermediary.FhirResourceMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext @@ -87,7 +86,7 @@ class TransformationRuleTest extends Specification { thrown(RuntimeException) } - def "runRule() throws RuleExecutionException when given a class without transform"() { + def "runRule() throws RuntimeException when given a class without transform"() { given: def ruleName = "Rule name" def ruleDescription = "Rule Description" @@ -105,10 +104,10 @@ class TransformationRuleTest extends Specification { rule.runRule(fhirResource) then: - thrown(RuleExecutionException) + thrown(RuntimeException) } - def "runRule() throws RuleExecutionException when given abstract class input"() { + def "runRule() throws RuntimeException when given abstract class input"() { given: def ruleName = "Rule name" def ruleDescription = "Rule Description" @@ -126,10 +125,10 @@ class TransformationRuleTest extends Specification { rule.runRule(fhirResource) then: - thrown(RuleExecutionException) + thrown(RuntimeException) } - def "runRule() throws RuleExecutionException when given a private constructor class input"() { + def "runRule() throws RuntimeException when given a private constructor class input"() { given: def ruleName = "Rule name" def ruleDescription = "Rule Description" @@ -147,6 +146,6 @@ class TransformationRuleTest extends Specification { rule.runRule(fhirResource) then: - thrown(RuleExecutionException) + thrown(RuntimeException) } } From 8333f556a5628c1386db9f423668138e6b759621 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 7 May 2024 13:44:54 -0700 Subject: [PATCH 144/151] Fixed test --- .../TransformationRuleEngineIntegrationTest.groovy | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy index d022d3c52..d86f55d3d 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineIntegrationTest.groovy @@ -35,6 +35,8 @@ class TransformationRuleEngineIntegrationTest extends Specification { TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.injectRegisteredImplementations() + + engine.ensureRulesLoaded() } def "transformation rules run without error"() { @@ -55,7 +57,7 @@ class TransformationRuleEngineIntegrationTest extends Specification { then: transformationMethodNames.each { transformationMethodName -> - assert TransformationRule.loadClassFromCache(transformationMethodName) != null + assert TransformationRule.getTransformationInstance(transformationMethodName) != null } } @@ -64,7 +66,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { def ruleName = 'addEtorProcessingTag' // we could also use this file for testing the rule: e2e/orders/001_OML_O21_short.fhir def bundle = new Bundle() - engine.ensureRulesLoaded() def rule = RuleEngineHelper.getRuleByName(engine.rules, ruleName) expect: @@ -84,8 +85,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { def ruleName = 'convertToOmlOrder' // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: 'ORM_O01') - - engine.ensureRulesLoaded() def rule = RuleEngineHelper.getRuleByName(engine.rules, ruleName) expect: @@ -106,8 +105,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { // we could also use this file for testing the rule: e2e/orders/003_2_ORM_O01_short_linked_to_002_ORU_R01_short.fhir def bundle = FhirBundleHelper.createMessageBundle(messageTypeCode: 'OML_O21') bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())) - - engine.ensureRulesLoaded() def rule = RuleEngineHelper.getRuleByName(engine.rules, ruleName) expect: @@ -137,7 +134,6 @@ class TransformationRuleEngineIntegrationTest extends Specification { FhirBundleHelper.resourceInBundle(bundle, Patient).contact.isEmpty() when: - engine.ensureRulesLoaded() transformationsToApply.each { ruleName -> def rule = RuleEngineHelper.getRuleByName(engine.rules, ruleName) rule.runRule(fhirResource) From 14d3e3d082275f3a10000d9a8063c1a2891ed686 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 8 May 2024 07:34:00 -0700 Subject: [PATCH 145/151] Using spock's ignore annotation instead of commenting test --- .../e2e/DemographicsTest.groovy | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy b/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy index efc36d781..882bce3e9 100644 --- a/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy +++ b/e2e/src/test/groovy/gov/hhs/cdc/trustedintermediary/e2e/DemographicsTest.groovy @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary.e2e +import spock.lang.Ignore import spock.lang.Specification import java.nio.file.Files @@ -29,25 +30,26 @@ class DemographicsTest extends Specification { parsedJsonBody.patientId == expectedPatientId } - // todo: ignoring while figuring out how to filter the demographics example - // def "payload file check"() { - // when: - // def response = demographicsClient.submit(newbornPatientJsonFileString, true) - // def parsedResponseBody = JsonParser.parseContent(response) - // def sentPayload = SentPayloadReader.read() - // def parsedSentPayload = JsonParser.parse(sentPayload) - // - // then: - // response.getCode() == 200 - // parsedSentPayload.entry[0].resource.resourceType == "MessageHeader" - // parsedSentPayload.entry[2].resource.resourceType == "ServiceRequest" - // - // parsedSentPayload.entry[1].resource.resourceType == "Patient" - // parsedSentPayload.entry[1].resource.id == "infant-twin-1" - // - // parsedSentPayload.entry[1].resource.identifier[1].value == parsedResponseBody.patientId //the second (index 1) identifier so happens to be the MRN - // parsedSentPayload.resourceType + "/" + parsedSentPayload.id == parsedResponseBody.fhirResourceId - // } + // flagging to delete if and when we remove demographics endpoint + @Ignore + def "payload file check"() { + when: + def response = demographicsClient.submit(newbornPatientJsonFileString, true) + def parsedResponseBody = JsonParser.parseContent(response) + def sentPayload = SentPayloadReader.read() + def parsedSentPayload = JsonParser.parse(sentPayload) + + then: + response.getCode() == 200 + parsedSentPayload.entry[0].resource.resourceType == "MessageHeader" + parsedSentPayload.entry[2].resource.resourceType == "ServiceRequest" + + parsedSentPayload.entry[1].resource.resourceType == "Patient" + parsedSentPayload.entry[1].resource.id == "infant-twin-1" + + parsedSentPayload.entry[1].resource.identifier[1].value == parsedResponseBody.patientId //the second (index 1) identifier so happens to be the MRN + parsedSentPayload.resourceType + "/" + parsedSentPayload.id == parsedResponseBody.fhirResourceId + } def "return a 400 response when request has unexpected format"() { given: From 95d2f963bbc717b1ee561cc51c141c96620006c3 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 8 May 2024 08:23:54 -0700 Subject: [PATCH 146/151] Added test coverage --- .../TransformationRuleEngine.java | 7 ++++++- .../RuleExecutionExceptionTest.groovy | 18 ++++++++++++++++++ .../ruleengine/RuleLoaderExceptionTest.groovy | 1 - .../TransformationRuleEngineTest.groovy | 11 +++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleExecutionExceptionTest.groovy diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java index c79312cc9..04038c189 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngine.java @@ -7,6 +7,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; +import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -44,7 +45,11 @@ public void ensureRulesLoaded() throws RuleLoaderException { getClass() .getClassLoader() .getResourceAsStream(ruleDefinitionsFileName); - assert resourceStream != null; + if (resourceStream == null) { + throw new RuleLoaderException( + "File not found: " + ruleDefinitionsFileName, + new FileNotFoundException()); + } List parsedRules = ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); this.rules.addAll(parsedRules); diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleExecutionExceptionTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleExecutionExceptionTest.groovy new file mode 100644 index 000000000..493b10f7c --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleExecutionExceptionTest.groovy @@ -0,0 +1,18 @@ +package gov.hhs.cdc.trustedintermediary.etor.ruleengine + +import spock.lang.Specification + +class RuleExecutionExceptionTest extends Specification { + def "constructor works"() { + given: + def message = "something blew up!" + def cause = new NullPointerException() + + when: + def exception = new RuleExecutionException(message, cause) + + then: + exception.getMessage() == message + exception.getCause() == cause + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderExceptionTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderExceptionTest.groovy index 5923a818d..a2ff9b6cc 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderExceptionTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleLoaderExceptionTest.groovy @@ -1,6 +1,5 @@ package gov.hhs.cdc.trustedintermediary.etor.ruleengine -import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException import gov.hhs.cdc.trustedintermediary.wrappers.HttpClientException import spock.lang.Specification diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy index a42d3182d..4381219f2 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/TransformationRuleEngineTest.groovy @@ -74,6 +74,17 @@ class TransformationRuleEngineTest extends Specification { 1 * mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) } + def "ensureRulesLoaded throws a RuleLoaderException if the file doesn't exist"() { + given: + def ruleEngine = TransformationRuleEngine.getInstance("nonexistent_file") + + when: + ruleEngine.ensureRulesLoaded() + + then: + thrown(RuleLoaderException) + } + def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) From b70f46da69915677d1c82958450901a702eaa5a6 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 8 May 2024 08:37:31 -0700 Subject: [PATCH 147/151] Added more test coverage --- .../validation/ValidationRuleEngine.java | 7 ++++++- .../etor/orders/SendOrderUseCaseTest.groovy | 16 ++++++++++++++++ .../etor/results/SendResultUseCaseTest.groovy | 16 ++++++++++++++++ .../validation/ValidationRuleEngineTest.groovy | 11 +++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java index 55098c566..33b39ec4e 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngine.java @@ -6,6 +6,7 @@ import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleLoaderException; import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference; +import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -43,7 +44,11 @@ public void ensureRulesLoaded() throws RuleLoaderException { getClass() .getClassLoader() .getResourceAsStream(ruleDefinitionsFileName); - assert resourceStream != null; + if (resourceStream == null) { + throw new RuleLoaderException( + "File not found: " + ruleDefinitionsFileName, + new FileNotFoundException()); + } List parsedRules = ruleLoader.loadRules(resourceStream, new TypeReference<>() {}); this.rules.addAll(parsedRules); diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy index f25f1b050..d5f0bf499 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy @@ -1,12 +1,15 @@ package gov.hhs.cdc.trustedintermediary.etor.orders +import gov.hhs.cdc.trustedintermediary.DemographicsMock import gov.hhs.cdc.trustedintermediary.OrderMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.demographics.Demographics import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageHelper import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataOrchestrator +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata @@ -127,4 +130,17 @@ class SendOrderUseCaseTest extends Specification { 1 * mockOrchestrator.findMessagesIdsToLink(_ as String) >> Set.of() 0 * mockOrchestrator.updateMetadataForSentMessage(_ as String, _ as String) } + + def "convertAndSend throws an UnableToSendMessageException when there's an error running the transformation rules"() { + given: + def order = Mock(Order) + mockEngine.runRules(order) >> { throw new RuleExecutionException("Error running transformation rules", new Exception()) } + TestApplicationContext.injectRegisteredImplementations() + + when: + SendOrderUseCase.getInstance().convertAndSend(order, "receivedId") + + then: + thrown(UnableToSendMessageException) + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy index a4583ceba..6751d1eff 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy @@ -8,6 +8,9 @@ import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageExceptio import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataOrchestrator +import gov.hhs.cdc.trustedintermediary.etor.orders.Order +import gov.hhs.cdc.trustedintermediary.etor.orders.SendOrderUseCase +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine import gov.hhs.cdc.trustedintermediary.wrappers.Logger import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata @@ -89,4 +92,17 @@ class SendResultUseCaseTest extends Specification { 1 * mockSender.send(result) >> Optional.of("sentId") 1 * mockLogger.logError(_, _) } + + def "convertAndSend throws an UnableToSendMessageException when there's an error running the transformation rules"() { + given: + def result = Mock(Result) + mockEngine.runRules(result) >> { throw new RuleExecutionException("Error running transformation rules", new Exception()) } + TestApplicationContext.injectRegisteredImplementations() + + when: + SendResultUseCase.getInstance().convertAndSend(result, "receivedId") + + then: + thrown(UnableToSendMessageException) + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy index de29c400a..4df90cd81 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/ruleengine/validation/ValidationRuleEngineTest.groovy @@ -74,6 +74,17 @@ class ValidationRuleEngineTest extends Specification { 1 * mockRuleLoader.loadRules(_ as InputStream, _ as TypeReference) } + def "ensureRulesLoaded throws a RuleLoaderException if the file doesn't exist"() { + given: + def ruleEngine = ValidationRuleEngine.getInstance("nonexistent_file") + + when: + ruleEngine.ensureRulesLoaded() + + then: + thrown(RuleLoaderException) + } + def "ensureRulesLoaded logs an error if there is an exception loading the rules"() { given: def exception = new RuleLoaderException("Error loading rules", new Exception()) From 7bd0a8231e4414b410a9b5028189db5e9fef0155 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 8 May 2024 09:07:07 -0700 Subject: [PATCH 148/151] Added remaining test coverage --- .../hapi/HapiOrderConverterTest.groovy | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index bc54a98a7..c2dc1d0b4 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -246,6 +246,7 @@ class HapiOrderConverterTest extends Specification { def entryList = new ArrayList() entryList.add(patientEntry) mockOrderBundle.setEntry(entryList) + when: HapiOrderConverter.addContactSectionToPatientResource(mockOrderBundle) @@ -256,6 +257,51 @@ class HapiOrderConverterTest extends Specification { !contactSection.hasName() } + def "findPatientOrNull returns expected result"() { + given: + def patientResource = fakePatientResource(true) + def patientEntry = new Bundle.BundleEntryComponent().setResource(patientResource) + def entryList = new ArrayList() + entryList.add(patientEntry) + mockOrderBundle.setEntry(entryList) + + when: + def patient = HapiOrderConverter.findPatientOrNull(mockOrderBundle) + + then: + patient != null + + when: + def patientNotFound = HapiOrderConverter.findPatientOrNull(new Bundle()) + + then: + patientNotFound == null + } + + def "findAllPatients returns expected result"() { + given: + def patientResource = fakePatientResource(true) + def patientEntry = new Bundle.BundleEntryComponent().setResource(patientResource) + def entryList = new ArrayList() + entryList.add(patientEntry) + mockOrderBundle.setEntry(entryList) + + when: + def patients = HapiOrderConverter.findAllPatients(mockOrderBundle) + List patientList = patients.collect { it as Patient } + + then: + patientList.size() == 1 + patientList.get(0) == patientResource + + when: + def patientsNotFound = HapiOrderConverter.findAllPatients(new Bundle()) + List patientsNotFoundList = patientsNotFound.collect { it as Patient } + + then: + patientsNotFoundList.size() == 0 + } + Patient fakePatientResource(boolean addHumanName) { def patient = new Patient() From 64ac1cc0e531b60c4225cad3b2945ab64c6bed3e Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 8 May 2024 10:27:17 -0700 Subject: [PATCH 149/151] Fixed conflicts from merge --- .../external/hapi/HapiOrderConverter.java | 52 ++-------------- .../hapi/HapiOrderConverterTest.groovy | 60 ++++++------------- 2 files changed, 25 insertions(+), 87 deletions(-) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java index 5b2439ae8..4d8df5de1 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java @@ -1,10 +1,6 @@ package gov.hhs.cdc.trustedintermediary.external.hapi; -import gov.hhs.cdc.trustedintermediary.etor.orders.Order; -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter; -import gov.hhs.cdc.trustedintermediary.wrappers.Logger; import java.util.List; -import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.MessageHeader; @@ -14,8 +10,8 @@ * Converts an order to identify as an HL7v2 OML in the {@link MessageHeader}. Also helps in moving * around data in the order. */ -public class HapiOrderConverter implements OrderConverter { - private static final HapiOrderConverter INSTANCE = new HapiOrderConverter(); +public class HapiOrderConverter { + private static final Coding OML_CODING = new Coding( "http://terminology.hl7.org/CodeSystem/v2-0003", @@ -27,37 +23,13 @@ public class HapiOrderConverter implements OrderConverter { new Coding( "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "MTH", "mother")); - @Inject Logger logger; - - @Inject HapiMessageConverterHelper hapiMessageConverterHelper; - - public static HapiOrderConverter getInstance() { - return INSTANCE; - } - - private HapiOrderConverter() {} - - @Override - public Order convertToOmlOrder(Order order) { - logger.logInfo("Converting order to have OML metadata"); - - var hapiOrder = (Order) order; - var orderBundle = hapiOrder.getUnderlyingResource(); - var messageHeader = hapiMessageConverterHelper.findOrInitializeMessageHeader(orderBundle); - + public static void convertToOmlOrder(Bundle order) { + var messageHeader = HapiMessageConverterHelper.findOrInitializeMessageHeader(order); messageHeader.setEvent(OML_CODING); - - return new HapiOrder(orderBundle); } - @Override - public Order addContactSectionToPatientResource(Order order) { - logger.logInfo("Adding contact section in Patient resource"); - - var hapiOrder = (Order) order; - var orderBundle = hapiOrder.getUnderlyingResource(); - - HapiHelper.resourcesInBundle(orderBundle, Patient.class) + public static void addContactSectionToPatientResource(Bundle order) { + HapiHelper.resourcesInBundle(order, Patient.class) .forEach( p -> { var myContact = p.addContact(); @@ -74,17 +46,5 @@ public Order addContactSectionToPatientResource(Order order) { myContact.setTelecom(p.getTelecom()); myContact.setAddress(p.getAddressFirstRep()); }); - - return new HapiOrder(orderBundle); - } - - @Override - public Order addEtorProcessingTag(Order message) { - var hapiOrder = (Order) message; - var messageBundle = hapiOrder.getUnderlyingResource(); - - hapiMessageConverterHelper.addEtorTagToBundle(messageBundle); - - return new HapiOrder(messageBundle); } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index a2d041530..a1990fa7a 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -2,16 +2,17 @@ package gov.hhs.cdc.trustedintermediary.external.hapi import gov.hhs.cdc.trustedintermediary.OrderMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter + import org.hl7.fhir.r4.model.Address -import org.hl7.fhir.r4.model.Bundle -import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.ContactPoint import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.HumanName +import org.hl7.fhir.r4.model.StringType + +import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.MessageHeader import org.hl7.fhir.r4.model.Patient -import org.hl7.fhir.r4.model.StringType import spock.lang.Specification class HapiOrderConverterTest extends Specification { @@ -23,13 +24,11 @@ class HapiOrderConverterTest extends Specification { def setup() { TestApplicationContext.reset() TestApplicationContext.init() - TestApplicationContext.register(OrderConverter, HapiOrderConverter.getInstance()) TestApplicationContext.register(HapiMessageConverterHelper, HapiMessageConverterHelper.getInstance()) TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) TestApplicationContext.injectRegisteredImplementations() mockPatient = new Patient() - mockOrderBundle = new Bundle().addEntry(new Bundle.BundleEntryComponent().setResource(mockPatient)) mockOrder = new OrderMock("fhirResourceId", "patientId", mockOrderBundle, null, null, null, null, null) } @@ -44,22 +43,26 @@ class HapiOrderConverterTest extends Specification { "ORM")))) when: - def convertedOrderBundle = HapiOrderConverter.getInstance().convertToOmlOrder(mockOrder).getUnderlyingResource() as Bundle + HapiOrderConverter.convertToOmlOrder(mockOrder.getUnderlyingResource()) then: - def convertedMessageHeader = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader + def convertedMessageHeader = + HapiHelper.resourcesInBundle(mockOrder.getUnderlyingResource(), MessageHeader.class).findFirst().orElse(null) + convertedMessageHeader != null convertedMessageHeader.getEventCoding().getCode() == "O21" convertedMessageHeader.getEventCoding().getDisplay().contains("OML") } def "adds the message header to specify OML"() { when: - def convertedOrderBundle = HapiOrderConverter.getInstance().convertToOmlOrder(mockOrder).getUnderlyingResource() as Bundle + HapiOrderConverter.convertToOmlOrder(mockOrder.getUnderlyingResource()) then: - def convertedMessageHeader = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader + def convertedMessageHeader = + HapiHelper.resourcesInBundle(mockOrder.getUnderlyingResource(), MessageHeader.class).findFirst().orElse(null) + convertedMessageHeader != null convertedMessageHeader.getEventCoding().getCode() == "O21" convertedMessageHeader.getEventCoding().getDisplay().contains("OML") } @@ -78,10 +81,10 @@ class HapiOrderConverterTest extends Specification { mockOrderBundle.setEntry(entryList) when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addContactSectionToPatientResource(mockOrder).getUnderlyingResource() as Bundle + HapiOrderConverter.addContactSectionToPatientResource(mockOrder.getUnderlyingResource()) then: - def convertedPatient = convertedOrderBundle.getEntry().get(0).getResource() as Patient + def convertedPatient = HapiHelper.resourcesInBundle(mockOrder.getUnderlyingResource(), Patient.class).findFirst().orElse(null) def contactSection = convertedPatient.getContact()[0] contactSection != null @@ -112,32 +115,6 @@ class HapiOrderConverterTest extends Specification { contactSectionAddress.getPostalCode() == convertedPatientAddress.getPostalCode() } - def "add etor processing tag to messageHeader resource"() { - given: - def expectedSystem = "http://localcodes.org/ETOR" - def expectedCode = "ETOR" - def expectedDisplay = "Processed by ETOR" - - def messageHeader = new MessageHeader() - messageHeader.setId(UUID.randomUUID().toString()) - def messageHeaderEntry = new Bundle.BundleEntryComponent().setResource(messageHeader) - mockOrderBundle.getEntry().add(1, messageHeaderEntry) - mockOrder.getUnderlyingResource() >> mockOrderBundle - - when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addEtorProcessingTag(mockOrder).getUnderlyingResource() as Bundle - - then: - def messageHeaders = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader - def actualSystem = messageHeaders.getMeta().getTag()[0].getSystem() - def actualCode = messageHeaders.getMeta().getTag()[0].getCode() - def actualDisplay = messageHeaders.getMeta().getTag()[0].getDisplay() - actualSystem == expectedSystem - actualCode == expectedCode - actualDisplay == expectedDisplay - } - - def "no humanName section in contact"() { given: def addHumanName = false @@ -146,12 +123,13 @@ class HapiOrderConverterTest extends Specification { def entryList = new ArrayList() entryList.add(patientEntry) mockOrderBundle.setEntry(entryList) + when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addContactSectionToPatientResource(mockOrder).getUnderlyingResource() as Bundle + HapiOrderConverter.addContactSectionToPatientResource(mockOrderBundle) then: - def convertedPatient = convertedOrderBundle.getEntry().get(0).getResource() as Patient - def contactSection = convertedPatient.getContact()[0] + def convertedPatient = HapiHelper.resourcesInBundle(mockOrderBundle, Patient.class).findFirst().orElse(null) + def contactSection = convertedPatient.getContact().first() !contactSection.hasName() } From abf4efaf4001c72f9506fdeeefdd18f2ad5df142 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 8 May 2024 10:31:33 -0700 Subject: [PATCH 150/151] Removed remaining demographics code --- .../custom/convertDemographicsToOrder.java | 20 ------------------- .../etor/orders/SendOrderUseCaseTest.groovy | 2 -- 2 files changed, 22 deletions(-) delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java deleted file mode 100644 index 6185b3d18..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertDemographicsToOrder.java +++ /dev/null @@ -1,20 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; - -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; -import java.util.Map; -import org.hl7.fhir.r4.model.Bundle; - -/** - * Custom transformation to convert a demographics message to an order. This class may be removed if - * we don't need to handle demographics. - */ -public class convertDemographicsToOrder implements CustomFhirTransformation { - - @Override - public void transform(FhirResource resource, Map args) { - Bundle bundle = (Bundle) resource.getUnderlyingResource(); - HapiOrderConverter.convertDemographicsToOrder(bundle); - } -} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy index d5f0bf499..a80c32d72 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy @@ -1,9 +1,7 @@ package gov.hhs.cdc.trustedintermediary.etor.orders -import gov.hhs.cdc.trustedintermediary.DemographicsMock import gov.hhs.cdc.trustedintermediary.OrderMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.demographics.Demographics import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageHelper import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata From 14d967e9cf14ba6af025efa26dd94d94faa20b15 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 8 May 2024 11:47:25 -0700 Subject: [PATCH 151/151] Converted HapiMessageConverter singleton into HapiMessageConverterHelper class with static methods --- .../etor/EtorDomainRegistration.java | 3 --- .../etor/orders/OrderConverter.java | 10 ---------- .../addContactSectionToPatientResource.java | 4 ++-- .../custom/convertToOmlOrder.java | 4 ++-- .../hapi/HapiMessageConverterHelper.java | 16 +--------------- ...ter.java => HapiOrderConverterHelper.java} | 8 ++------ .../PartnerMetadataOrchestratorTest.groovy | 2 -- .../HapiMessageConverterHelperTest.groovy | 19 ++++++++++--------- ...vy => HapiOrderConverterHelperTest.groovy} | 11 +++++------ 9 files changed, 22 insertions(+), 55 deletions(-) delete mode 100644 etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderConverter.java rename etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/{HapiOrderConverter.java => HapiOrderConverterHelper.java} (89%) rename etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/{HapiOrderConverterTest.groovy => HapiOrderConverterHelperTest.groovy} (93%) diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java index 6efba951f..94ee5a494 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java @@ -34,7 +34,6 @@ import gov.hhs.cdc.trustedintermediary.external.database.DatabasePartnerMetadataStorage; import gov.hhs.cdc.trustedintermediary.external.database.DbDao; import gov.hhs.cdc.trustedintermediary.external.database.PostgresDao; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiPartnerMetadataConverter; import gov.hhs.cdc.trustedintermediary.external.localfile.FileMessageLinkStorage; @@ -101,8 +100,6 @@ public Map> domainRegistra ApplicationContext.register(SendResultUseCase.class, SendResultUseCase.getInstance()); ApplicationContext.register(ResultSender.class, ReportStreamResultSender.getInstance()); // Message - ApplicationContext.register( - HapiMessageConverterHelper.class, HapiMessageConverterHelper.getInstance()); ApplicationContext.register( ReportStreamSenderHelper.class, ReportStreamSenderHelper.getInstance()); ApplicationContext.register(HapiMessageHelper.class, HapiMessageHelper.getInstance()); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderConverter.java deleted file mode 100644 index dee2f1447..000000000 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/OrderConverter.java +++ /dev/null @@ -1,10 +0,0 @@ -package gov.hhs.cdc.trustedintermediary.etor.orders; - -/** Interface for converting things to orders and things in orders. */ -public interface OrderConverter { - Order convertToOmlOrder(Order order); - - Order addContactSectionToPatientResource(Order order); - - Order addEtorProcessingTag(Order message); -} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java index ae1d67880..6ab116edd 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/addContactSectionToPatientResource.java @@ -4,7 +4,7 @@ import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverterHelper; import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; @@ -18,7 +18,7 @@ public class addContactSectionToPatientResource implements CustomFhirTransformat @Override public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); - HapiOrderConverter.addContactSectionToPatientResource(bundle); + HapiOrderConverterHelper.addContactSectionToPatientResource(bundle); metadata.put(bundle.getId(), EtorMetadataStep.CONTACT_SECTION_ADDED_TO_PATIENT); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java index e59281844..80d7dfb02 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/transformation/custom/convertToOmlOrder.java @@ -4,7 +4,7 @@ import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverterHelper; import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; @@ -18,7 +18,7 @@ public class convertToOmlOrder implements CustomFhirTransformation { @Override public void transform(FhirResource resource, Map args) { Bundle bundle = (Bundle) resource.getUnderlyingResource(); - HapiOrderConverter.convertToOmlOrder(bundle); + HapiOrderConverterHelper.convertToOmlOrder(bundle); metadata.put(bundle.getId(), EtorMetadataStep.ORDER_CONVERTED_TO_OML); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java index 8fbf3a8ae..4547ec78a 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelper.java @@ -1,27 +1,13 @@ package gov.hhs.cdc.trustedintermediary.external.hapi; -import gov.hhs.cdc.trustedintermediary.wrappers.Logger; -import javax.inject.Inject; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Meta; -/** - * Helper class with a variety of utilities to use on a FHIR bundle message. It adds the 'ETOR' tag - * to a FHIR bundle of type: OML, ORU It also creates the messageHeader resource in a FHIR bundle - * message. - */ +/** Helper class with transformation methods that take a FHIR Bundle and modifies it */ public class HapiMessageConverterHelper { - private static final HapiMessageConverterHelper INSTANCE = new HapiMessageConverterHelper(); - - public static HapiMessageConverterHelper getInstance() { - return INSTANCE; - } - - @Inject Logger logger; - private HapiMessageConverterHelper() {} public static void addEtorTagToBundle(Bundle messageBundle) { diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterHelper.java similarity index 89% rename from etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java rename to etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterHelper.java index 4d8df5de1..ff69ee0f8 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterHelper.java @@ -3,14 +3,10 @@ import java.util.List; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Patient; -/** - * Converts an order to identify as an HL7v2 OML in the {@link MessageHeader}. Also helps in moving - * around data in the order. - */ -public class HapiOrderConverter { +/** Helper class with transformation methods that take an order Bundle and modifies it */ +public class HapiOrderConverterHelper { private static final Coding OML_CODING = new Coding( diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy index c316e28a8..cd81b33fe 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/metadata/partner/PartnerMetadataOrchestratorTest.groovy @@ -5,8 +5,6 @@ import gov.hhs.cdc.trustedintermediary.etor.RSEndpointClient import gov.hhs.cdc.trustedintermediary.etor.messagelink.MessageLink import gov.hhs.cdc.trustedintermediary.etor.messagelink.MessageLinkStorage import gov.hhs.cdc.trustedintermediary.etor.messages.MessageHdDataType -import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.TransformationRuleEngine -import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamEndpointClientException import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelperTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelperTest.groovy index 13b88d50f..12e60c49c 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelperTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageConverterHelperTest.groovy @@ -2,7 +2,11 @@ package gov.hhs.cdc.trustedintermediary.external.hapi import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import org.hl7.fhir.r4.model.* +import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.MessageHeader +import org.hl7.fhir.r4.model.Meta +import org.hl7.fhir.r4.model.Patient import spock.lang.Specification class HapiMessageConverterHelperTest extends Specification { @@ -17,7 +21,6 @@ class HapiMessageConverterHelperTest extends Specification { mockBundle = null TestApplicationContext.reset() TestApplicationContext.init() - TestApplicationContext.register(HapiMessageConverterHelper, HapiMessageConverterHelper.getInstance()) TestApplicationContext.injectRegisteredImplementations() mockPatient = new Patient() @@ -32,7 +35,7 @@ class HapiMessageConverterHelperTest extends Specification { mockBundle.getEntry().add(messageHeaderEntry) when: - HapiMessageConverterHelper.getInstance().addEtorTagToBundle(mockBundle) as Bundle + HapiMessageConverterHelper.addEtorTagToBundle(mockBundle) then: def messageHeaders = mockBundle.getEntry().get(1).getResource() as MessageHeader @@ -45,7 +48,7 @@ class HapiMessageConverterHelperTest extends Specification { def "addEtorTag adds the ETOR message header tag to any Bundle when message header is missing"() { when: - HapiMessageConverterHelper.getInstance().addEtorTagToBundle(mockBundle) as Bundle + HapiMessageConverterHelper.addEtorTagToBundle(mockBundle) then: def messageHeaders = mockBundle.getEntry().get(1).getResource() as MessageHeader @@ -71,7 +74,7 @@ class HapiMessageConverterHelperTest extends Specification { mockBundle.getEntry().add(messageHeaderEntry) when: - HapiMessageConverterHelper.getInstance().addEtorTagToBundle(mockBundle) as Bundle + HapiMessageConverterHelper.addEtorTagToBundle(mockBundle) then: def messageHeaders = mockBundle.getEntry().get(1).getResource() as MessageHeader @@ -97,12 +100,10 @@ class HapiMessageConverterHelperTest extends Specification { def messageHeaderEntry = new Bundle.BundleEntryComponent().setResource(messageHeader) mockBundle.getEntry().add(messageHeaderEntry) - def converter = HapiMessageConverterHelper.getInstance() - when: - converter.addEtorTagToBundle(mockBundle) + HapiMessageConverterHelper.addEtorTagToBundle(mockBundle) messageHeader.getMeta().getTag().findAll {it.system == etorTag.system}.size() == 1 - converter.addEtorTagToBundle(mockBundle) + HapiMessageConverterHelper.addEtorTagToBundle(mockBundle) then: messageHeader.getMeta().getTag().findAll {it.system == etorTag.system}.size() == 1 diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterHelperTest.groovy similarity index 93% rename from etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy rename to etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterHelperTest.groovy index a1990fa7a..fecfd41f2 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterHelperTest.groovy @@ -15,7 +15,7 @@ import org.hl7.fhir.r4.model.MessageHeader import org.hl7.fhir.r4.model.Patient import spock.lang.Specification -class HapiOrderConverterTest extends Specification { +class HapiOrderConverterHelperTest extends Specification { Patient mockPatient Bundle mockOrderBundle @@ -24,7 +24,6 @@ class HapiOrderConverterTest extends Specification { def setup() { TestApplicationContext.reset() TestApplicationContext.init() - TestApplicationContext.register(HapiMessageConverterHelper, HapiMessageConverterHelper.getInstance()) TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) TestApplicationContext.injectRegisteredImplementations() @@ -43,7 +42,7 @@ class HapiOrderConverterTest extends Specification { "ORM")))) when: - HapiOrderConverter.convertToOmlOrder(mockOrder.getUnderlyingResource()) + HapiOrderConverterHelper.convertToOmlOrder(mockOrder.getUnderlyingResource()) then: def convertedMessageHeader = @@ -56,7 +55,7 @@ class HapiOrderConverterTest extends Specification { def "adds the message header to specify OML"() { when: - HapiOrderConverter.convertToOmlOrder(mockOrder.getUnderlyingResource()) + HapiOrderConverterHelper.convertToOmlOrder(mockOrder.getUnderlyingResource()) then: def convertedMessageHeader = @@ -81,7 +80,7 @@ class HapiOrderConverterTest extends Specification { mockOrderBundle.setEntry(entryList) when: - HapiOrderConverter.addContactSectionToPatientResource(mockOrder.getUnderlyingResource()) + HapiOrderConverterHelper.addContactSectionToPatientResource(mockOrder.getUnderlyingResource()) then: def convertedPatient = HapiHelper.resourcesInBundle(mockOrder.getUnderlyingResource(), Patient.class).findFirst().orElse(null) @@ -125,7 +124,7 @@ class HapiOrderConverterTest extends Specification { mockOrderBundle.setEntry(entryList) when: - HapiOrderConverter.addContactSectionToPatientResource(mockOrderBundle) + HapiOrderConverterHelper.addContactSectionToPatientResource(mockOrderBundle) then: def convertedPatient = HapiHelper.resourcesInBundle(mockOrderBundle, Patient.class).findFirst().orElse(null)