Using Business Rules (For Developers)
Models
ValidationRule: Represents the overall rule which is run in a specific context
ValidationRuleIf: Represents a single If Expression within a ValidationRule
ValidationRuleThen: Represents a single Then Expression within a ValidationRule
APIs
Server-side: ValidationService
ExtJS pages: One.ext.plugin.BusinessRulesPlugin
React pages: BusinessRules
Steps for manual integration
Make the Business Rules Management UIs available to the user
Permissions should be added for the models listed above for the role types that will be managing Business Rules.
The Business Rules report is accessible through the WebAction provided by PLT:
<WebActionRefname="ValidationRules"module="PLT"/>
Register the Business Rule Context
Registration of the Business Rule Context is generally done on server startup, so ModuleContextListener is the best place for that:
publicclassModuleContextListenerimplementscom.onenetwork.platform.env.module.ModuleContextListener {publicvoidmoduleInitialized(Module module)throwsException {registerBusinessRuleContext();}// If this list is not provided, the Context will default to only supporting FullContextValidation.privatestaticfinalList<String> SUPPORTED_RULE_SUBTYPES = Arrays.asList("FullContextValidation","FieldEditability","FieldValidation","FieldEnumerationRule","FieldDefaultValue","FieldComputedValue");privatevoidregisterBusinessRuleContext() {ValidationService VS = Services.get(ValidationService.class);UserContextService UCS = Services.get(UserContextService.class);PlatformUserContext userContext = UCS.createDefaultInstanceAdminContext();// TestModel is a 2-level model with TestModelChild being the 2nd level// Create a single SubContext for TestModelMap<String, Object> valueBindings = VS.createModelBindings(newTestModel());List<Pair<String, String>> labelBindings = valueToLabelBindings(valueBindings);ValidationSubContext testModelSubContext =newValidationSubContext("TestModel",false, labelBindings);// Create a multi SubContext for TestModelChildvalueBindings = VS.createModelBindings(newTestModelChild());labelBindings = valueToLabelBindings(valueBindings);ValidationSubContext testModelChildSubContext =newValidationSubContext("TestModelChild",true, labelBindings);// Register the ContextValidationContext testModelContext =newValidationContext("TestModel",Arrays.asList(testModelSubContext, testModelChildSubContext),SUPPORTED_RULE_SUBTYPES);VS.registerContext(testModelContext);}privateList<Pair<String, String>> valueToLabelBindings(Map<String, Object> valueBindings) {returnvalueBindings.keySet().stream().map(k ->newPair<>(k,"meta.field."+ k)).collect(Collectors.toList());}}
-
Add code to fetch and evaluate the Business Rules
Server
On the server, it usually makes sense to evaluate Business Rules within a workflow, right before data changes are persisted to the database. But ultimately it depends on how the Business Rules are integrated into a feature.
Overall approach:Fetch the Business Rules from the database.
Build a ValidationRequest using the list of ValidationRules.
For each model:
Bind the values from the model to the ValidationRequest.
Use ValidationService.validate() to evaluate the ValidationRequest and return a ValidationResponse.
Use the ValidationResponse and ValidationService.applyValidatedStateToModel() to apply results from the Business Rules back onto the model.
publicclassValidateTestModelextendsAbstractActionBasedWorkflowActivity<TestModel> {privateWorkflowService WS;privateValidationService VS;privateModelDataService MDS;privateUserContextService UCS;publicValidateTestModel() {WS = Services.get(WorkflowService.class);VS = Services.get(ValidationService.class);MDS = Services.get(ModelDataService.class);UCS = Services.get(UserContextService.class);}@Overridepublicvoidexecute(ActionBasedWorkflowContext<TestModel> context) {WS.populateCurrentFromDatabase(context);WS.mergeInputToCurrent(context);evaluateBusinessRules(context);WS.write(context.getCurrent().getModels(), context);}privatevoidevaluateBusinessRules(ActionBasedWorkflowContext<TestModel> context) {ValidationRequest validationRequest = buildValidationRequest(context);for(TestModel model : context.getCurrent().getModels()) {bindValues(validationRequest, model, context);ValidationResponse validationResponse = VS.validate(validationRequest, context.getPlatformUserContext());applyResponseToModel(validationResponse, model, context);}}privateValidationRequest buildValidationRequest(ActionBasedWorkflowContext<TestModel> context) {// Fetch the Business Rules from the DB for this specific Context.List<ValidationRule> rules = MDS.read(ValidationRule.class,UCS.createDefaultInstanceAdminContext(),null,ModelQuery.sqlFilter("CONTEXT = 'TestModel'"));List<ValidationPackage> validationPackages = VS.createValidationPackages(rules, context.getPlatformUserContext());returnnewValidationRequest(validationPackages);}privatevoidbindValues(ValidationRequest validationRequest, TestModel model, ActionBasedWorkflowContext<TestModel> context) {Map<String, Object> bindings = VS.createModelBindings(model);validationRequest.bindSingle("TestModel","TestModel", bindings);List<Map<String, Object>> childBindings = model.getTestModelChilds().stream().map((childModel) -> VS.createModelBindings(childModel)).collect(Collectors.toList());validationRequest.bindMultiple("TestModel","TestModelChild", childBindings);}privatevoidapplyResponseToModel(ValidationResponse validationResponse, TestModel model, ActionBasedWorkflowContext<TestModel> context) {// TestModelList<ValidatedModelState> validatedModelStates = validationResponse.getValidatedModelStatesForContext("TestModel","TestModel");if(!validatedModelStates.isEmpty()) {VS.applyValidatedStateToModel(validatedModelStates.get(0), model);}// TestModelChildList<ValidatedModelState> validatedChildStates = validationResponse.getValidatedModelStatesForContext("TestModel","TestModelChild");for(inti =0; i < validatedChildStates.size(); ++i) {VS.applyValidatedStateToModel(validatedChildStates.get(i), model.getTestModelChilds().get(i));}if(!(validationResponse.getValidationFailedErrorMessages().isEmpty())) {model.setError(validationResponse.getValidationFailedErrorMessages().get(0));}}}UI
On the UI, only running Business Rules inside ExtJS pages is currently supported. The One.ext.plugin.BusinessRulesPlugin class can be embedded into a Model Form or custom page, and there is also built-in support for User-defined Pages.
For getting Business Rules working inside a User-defined Page, a ValidationRequest needs to be provided to the page when the user loads it. To do that, you can register a ValidationRequestProvider within the ModuleContextListener. The ValidationRequestProvider callback method is passed information about the page and should return the ValidationRequest, if applicable:publicclassModuleContextListenerimplementscom.onenetwork.platform.env.module.ModuleContextListener {publicvoidmoduleInitialized(Module module)throwsException {registerBusinessRuleContext();registerValidationRequestProvider();}privatevoidregisterValidationRequestProvider() {finalValidationService VS = Services.get(ValidationService.class);finalUserContextService UCS = Services.get(UserContextService.class);finalModelDataService MDS = Services.get(ModelDataService.class);ValidationRequestProviderRegistry.register(newValidationRequestProvider() {@OverridepublicOptional<ValidationRequest> provide(ModelLevelType modelLevelType, Long sysId, String actionName, PlatformUserContext userContext) {// Ignore models other than our TestModelif(!"MDMT.TestModel".equals(modelLevelType.getValue())) {returnOptional.empty();}PlatformUserContext adminContext = UCS.createDefaultInstanceAdminContext();List<ValidationRule> rules = MDS.read(ValidationRule.class, adminContext,null, ModelQuery.sqlFilter("CONTEXT = 'TestModel'"));List<ValidationPackage> packages = VS.createValidationPackages(rules, adminContext);ValidationRequest validationRequest =newValidationRequest(packages);TestModel testModel =null;if(sysId !=null) {testModel = MDS.readById(TestModel.class, sysId, userContext);}// Bind values from TestModelMap<String, Object> valueBindings = testModel !=null? VS.createModelBindings(testModel) :newHashMap<>();validationRequest.bindSingle("TestModel","TestModel", valueBindings);if(testModel !=null) {// Bind values from every TestModelChildList<Map<String, Object>> childLevelValueBindings =newArrayList<>();for(TestModelChild testModelChild : testModel.getTestModelChilds()) {childLevelValueBindings.add(VS.createModelBindings(testModelChild));}validationRequest.bindMultiple("TestModel","TestModelChild", childLevelValueBindings);}returnOptional.of(validationRequest);}});}}
Steps for automatic integration (Model-based)
Platform has a convenience feature for adding support for Business Rule integration by auto-generating Contexts that are directly based on Models. As part of this feature, Platform also provides a global callback to evaluate Business Rules created against these Contexts. Everything is configured through the ModelValidatorConfig model. When validation rules fail, it can be configured to reject the records with errors or to accept the records and generate WBProblems.
ModelValidatorConfig
This model contains the following fields:
FlowName: This is the name which will be given to the ValidationContext for this configuration.
TargetModelType: Model for which validation should be enabled when actions are executed.
Actions: Defines the JSON Array of action names which should be considered for validation. If omitted, applies to all actions.
ValidationFailureHandlerClass: An optional fully-qualified name of a class implementing com.onenetwork.platform.data.validation.ValidationFailureHandler. If provided, this will be called any time validation rules fail for this context.
ValidationEnterpriseLookupClass: If provided, it should be the fully-qualified name of a class implementing ValidationEnterpriseLookup. The class should receive a List of Model, and should return a new List with the id of an enterprise for each model. That id will be used to lookup the validation rules to be executed on that model. the list should have nulls if VC validations should be used instead. Examples of what this interface might do would be to return the TransportationControllingOrg's parent ent on a shipment.
Blocking: If Blocking is set to true, an Error will be set on the model if validation fails.
CreateWBProblem: If CreateWBProblem is set to true and Blocking is set to false, then WBProblem will be created automatically for a failed validation rule.
WBProblemPriority: WBProblemPriority defines the problem's priority to be created if CreateWBProblem is set as true for current configuration. Possible values for this are LOW, MEDIUM and HIGH.
WBProblemPopulator: If provided, it should be the fully-qualified name of a class implementing ValidationWBProblemPopulator. This class will be taken into consideration to populate the WBProblem details.