generated from CDCgov/template
-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rules engine refactoring #1037
Merged
Merged
Rules engine refactoring #1037
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
f5e94fe
Refactored validation rule engine in preparation for tranformation en…
basiliskus 28505a5
Fixed instantiation
basiliskus 2c3f69d
Merge branch 'main' into story/1024/transformation-engine
basiliskus bdd568a
Refactored to make Rule interface more generic
basiliskus f65cede
Fixed tests
basiliskus 0cf5640
Update transformation_definitions.json
luis-pabon-tf a08c687
Merge branch 'main' into story/1024/transformation-engine
basiliskus 8631334
Added missing override tags
basiliskus 6214e84
Fixed arg naming
basiliskus 4756364
Use generics to deserialize correct type of Rule implementation
basiliskus 1de28ef
Converted RuleEngine back into a singleton and added parameters
basiliskus 5c8073b
Refactored to have a separate implementation for ValidationRuleEngine
basiliskus 45a3bca
Refactored to pass TypeReference and fix mapping issue
basiliskus d5490e8
Added missing override tags
basiliskus 8e7398c
Fixed tests in ValidationRuleEngineIntegrationTest
basiliskus dbd8043
Fixed more tests
basiliskus d399054
Removed transformation engine implementation to move into new PR
basiliskus 76b7d82
Update ValidationRuleTest.groovy
luis-pabon-tf 27ea75d
Update ValidationRuleEngineTest.groovy
luis-pabon-tf 0f1b275
Update ValidationRuleEngine.java
luis-pabon-tf 5acab7a
Update ValidationRuleEngineTest.groovy
luis-pabon-tf 28b0d7a
Update ValidationRuleEngineTest.groovy
luis-pabon-tf 1f57bb1
Update Rule loading to handle Path for flexibility
saquino0827 5d4dd9e
Merge branch 'story/1024/transformation-engine' of https://github.com…
saquino0827 34a2bf9
Fixed remaining test
basiliskus e137c1d
Fixed actual remaining test
basiliskus 0fc78c5
Changed naming
basiliskus 95d4b2d
Update ValidationRuleEngineTest.groovy
luis-pabon-tf a27a876
Merge branch 'story/1024/transformation-engine' of https://github.com…
luis-pabon-tf c0f0093
Added test coverage
basiliskus 2a5f7ab
Updated javadocs
basiliskus 4a5068d
Created trasnformation package and moved transformation engine files
basiliskus 2bd21c8
Changed implementation for rules implementation to inherit from Rule …
basiliskus 6ba1943
Clean up
basiliskus a03dd0b
Added missing override tag
basiliskus e9290aa
Create RuleTest.groovy
luis-pabon-tf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 70 additions & 13 deletions
83
etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/Rule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +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<String> conditions; | ||
private List<String> rules; | ||
|
||
/** | ||
* 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! | ||
* Do not delete this constructor! It is used for JSON deserialization when loading rules from a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, Jackson requires an empty constructor to de-serialize things, don't have to add this but something to think about |
||
* file. | ||
*/ | ||
String getViolationMessage(); | ||
public Rule() {} | ||
|
||
List<String> getConditions(); | ||
public Rule( | ||
String ruleName, | ||
String ruleDescription, | ||
String ruleMessage, | ||
List<String> ruleConditions, | ||
List<String> ruleActions) { | ||
name = ruleName; | ||
description = ruleDescription; | ||
message = ruleMessage; | ||
conditions = ruleConditions; | ||
rules = ruleActions; | ||
} | ||
|
||
List<String> getValidations(); | ||
public String getName() { | ||
return name; | ||
} | ||
|
||
boolean isValid(FhirResource<?> resource); | ||
public String getDescription() { | ||
return description; | ||
} | ||
|
||
boolean appliesTo(FhirResource<?> resource); | ||
public String getMessage() { | ||
return message; | ||
} | ||
|
||
public List<String> getConditions() { | ||
return conditions; | ||
} | ||
|
||
public List<String> 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."); | ||
} | ||
} |
66 changes: 8 additions & 58 deletions
66
etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/RuleEngine.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,13 @@ | ||
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; | ||
/** | ||
* 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(); | ||
|
||
/** Manages the application of rules loaded from a definitions file using the RuleLoader. */ | ||
public class RuleEngine { | ||
void ensureRulesLoaded() throws RuleLoaderException; | ||
|
||
private static final RuleEngine INSTANCE = new RuleEngine(); | ||
|
||
final List<Rule> rules = new ArrayList<>(); | ||
|
||
@Inject Logger logger; | ||
@Inject RuleLoader ruleLoader; | ||
|
||
private RuleEngine() {} | ||
|
||
public static RuleEngine getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
public void unloadRules() { | ||
rules.clear(); | ||
} | ||
|
||
public void ensureRulesLoaded() { | ||
if (rules.isEmpty()) { | ||
synchronized (this) { | ||
if (rules.isEmpty()) { | ||
loadRules(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
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); | ||
} | ||
} | ||
|
||
public void validate(FhirResource<?> resource) { | ||
logger.logDebug("Validating FHIR resource"); | ||
ensureRulesLoaded(); | ||
for (Rule rule : rules) { | ||
if (rule.appliesTo(resource) && !rule.isValid(resource)) { | ||
logger.logWarning("Rule violation: " + rule.getViolationMessage()); | ||
} | ||
} | ||
} | ||
void runRules(FhirResource<?> resource); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 0 additions & 108 deletions
108
etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/ValidationRule.java
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first value in register should be an interface and the second should be the class that is defining the behavior for an interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As commented above, we need to be able to identify two different implementations of the rule engine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on doing a parent interface (RuleEngine), and two child interfaces that will extend the parent interface (ValidationRuleEngine, TransformationRuleEngine)? With this setup, you can use the child interface to register the implementation for each (ValidationRuleEngineImp, TransformationRuleEngineImp). The only challenge here is the naming for the parent interface, the child interfaces, and the implementations for each child interface. You register both engines with a unique interface for each.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the value we register in the ApplicationContext needs to be an interface, a lot of the existing ones are not. Is there a benefit in creating the interface even if it will have only one implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we generally wrap 3rd party dependencies in an interface to reduce dependencies