I have come across a challenge when saving metadata definitions to the database in code without having to use a liquibase changeset, and I was wondering whether there is an standardized approach to this pattern. The scenario below is from the Data Integrity module:
- The Data Integrity Rule class defines a rule that is to be executed
- The [RuleDefn] (https://github.com/openmrs/openmrs-module-dataintegrity/blob/b70c1a452b4995237766f566d1f804992359b59e/dataintegrity-api/src/main/java/org/openmrs/module/dataintegrity/rule/RuleDefn.java) is the interface that is to be implemented by a class whose data is saved by the rule class in 1 above
What I would like to do is:
- Add metadata to all implementations of the RuleDefn class which is saved as a Data Integrity Rule
- Have a manager to save all implementations of the RuleDefn class to the database so that once a class that implements RuleDefn is added to the code base it is auto-magically saved (similar to what Reporting does with Report Definitions)
Thanks in advance
@ssmusoke are you simply looking for a standard approach to use? Or are you reporting failure to save definitions?
@dkayiwa I am looking for a standard approach to use with examples
@ssmusoke how about doing exactly what Reporting does with Report Definitions?
My approach ended up being as follows:
Created an abstract class BasePatientRuleDefinition which extended the RuleDefn interface, and had one abstract method getRule() hence has to be implemented by all subclasses - and does not pollute the interfaces of the main module
Added an @Component annotation to each subclass
In my activator used Context.getRegisteredComponents(basePatientRuleDefinition) to load all rule classes and called the getRule() method of each sub-class to get its rule definition and save it to the database.
Thanks @ssmusoke for sharing your solution!
Though i would go for BaseRuleDefinition instead of BasePatientRuleDefinition.
On the other hand, i would be in favour of an interface to class.
@dkayiwa The RuleDefinitions support Patient and Program classes, so there will be a BaseProgramRuleDefinition too for those validation rules written for programs.
I have made it an abstract class to provide utility methods for sub-classes to reduce code duplication, such as getting specific patient identifiers etc.
@ssmusoke i would still do an interface and make Context.getRegisteredComponents() look for the interface. Then have the abstract base class implement this interface to provide the reusable methods. That way, if another class wants to take advantage of this but already has another base class, they do not lose out. You know Java does not support multiple class inheritance!
@dkayiwa Thanks that was a dilema that I was having.
Now thinking aloud, this would mean that I can move the ability to save metadata from my custom module into the core module (in this case DataIntegrity) so providing the service free for those who may need it.
I think I agree with Daniel, it’s good practice to pass an interface to Context.getRegisteredComponents, then in your code you can check if a rule is a subclass of the base class.
@wyclif got you. Strange question - what to name the interface.
I already have a RuleDefintion interface, BaseRuleDefinition does not sound right … any ideas? All the interface has is a getRule() class with returns a DataIntegrityRule that is saved to the database.
I thought there’s already an interface in the data integrity module that all Rule definitions implement and you would use that
@wyclif I am not sure about forcing every user to have to provide metadata that is immediately discoverable and deployable.
@bharatak @sdeepak your thoughts?
If you do not want to force everyone to provide metadata, the you could call the interface RuleDefinitionMetadata
@ssmusoke Are you thinking of options for implementers to either implement RuleDefinitionInterface or to extend BasePatientRuleDefinition providing the rule metadata? Why not be opinionated and have getRule() implemented for all Rule Definitions?
@bharatak @dkayiwa Thanks, I have added the method to the RuleDefinition interface, however an implementer can return null and use other methods to save the rule to the database
I’m having a bit of trouble following this thread.
Some big-picture comments…
Recently, @mseaton have come to believe that Bahmni has generally approached these things in a better way, with the idea that the implementation has a “config” which is a folder with various config files in it, that can still be source-controlled, but these details don’t have to be packaged as an OpenMRS module (thus making it easier for implementations to work with).
OpenMRS and its various modules should know to read their own config from a certain folder at startup, and do whatever. Our first time doing this in the reporting module was in REPORT-761.
Incidentally that ticket also shows an example of loading implementer-defined groovy code.
(Hope this is helpful/relevant.)
@darius @mseaton I’d like to document the process defined in REPORT-761 on the OpenMRS wiki. Has this been started anywhere?
If I have a generic tomcat7 installation at /var/lib/tomcat7/ and the runtime properties at /usr/share/tomcat7/.OpenMRS/ What would be the $APPDATA folder?
@craigappl, I don’t believe I started documenting it - @darius may have. I’ll let him answer that.
The $APPDATA folder in your case would be /usr/share/tomcat7/.OpenMRS/.
Thanks for taking this on and documenting it!
@craigappl I did not start documenting this, no. I think my comments on the issues should be pretty clear about what I implemented (though they are long), but if you run into any questions, ask me.