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:
<
WebActionRef
name
=
"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:
public
class
ModuleContextListener
implements
com.onenetwork.platform.env.module.ModuleContextListener {
public
void
moduleInitialized(Module module)
throws
Exception {
registerBusinessRuleContext();
}
// If this list is not provided, the Context will default to only supporting FullContextValidation.
private
static
final
List<String> SUPPORTED_RULE_SUBTYPES = Arrays.asList(
"FullContextValidation"
,
"FieldEditability"
,
"FieldValidation"
,
"FieldEnumerationRule"
,
"FieldDefaultValue"
,
"FieldComputedValue"
);
private
void
registerBusinessRuleContext() {
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 TestModel
Map<String, Object> valueBindings = VS.createModelBindings(
new
TestModel());
List<Pair<String, String>> labelBindings = valueToLabelBindings(valueBindings);
ValidationSubContext testModelSubContext =
new
ValidationSubContext(
"TestModel"
,
false
, labelBindings);
// Create a multi SubContext for TestModelChild
valueBindings = VS.createModelBindings(
new
TestModelChild());
labelBindings = valueToLabelBindings(valueBindings);
ValidationSubContext testModelChildSubContext =
new
ValidationSubContext(
"TestModelChild"
,
true
, labelBindings);
// Register the Context
ValidationContext testModelContext =
new
ValidationContext(
"TestModel"
,
Arrays.asList(testModelSubContext, testModelChildSubContext),
SUPPORTED_RULE_SUBTYPES);
VS.registerContext(testModelContext);
}
private
List<Pair<String, String>> valueToLabelBindings(Map<String, Object> valueBindings) {
return
valueBindings.keySet().stream().map(
k ->
new
Pair<>(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.
public
class
ValidateTestModel
extends
AbstractActionBasedWorkflowActivity<TestModel> {
private
WorkflowService WS;
private
ValidationService VS;
private
ModelDataService MDS;
private
UserContextService UCS;
public
ValidateTestModel() {
WS = Services.get(WorkflowService.
class
);
VS = Services.get(ValidationService.
class
);
MDS = Services.get(ModelDataService.
class
);
UCS = Services.get(UserContextService.
class
);
}
@Override
public
void
execute(ActionBasedWorkflowContext<TestModel> context) {
WS.populateCurrentFromDatabase(context);
WS.mergeInputToCurrent(context);
evaluateBusinessRules(context);
WS.write(context.getCurrent().getModels(), context);
}
private
void
evaluateBusinessRules(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);
}
}
private
ValidationRequest 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());
return
new
ValidationRequest(validationPackages);
}
private
void
bindValues(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);
}
private
void
applyResponseToModel(ValidationResponse validationResponse, TestModel model, ActionBasedWorkflowContext<TestModel> context) {
// TestModel
List<ValidatedModelState> validatedModelStates = validationResponse.getValidatedModelStatesForContext(
"TestModel"
,
"TestModel"
);
if
(!validatedModelStates.isEmpty()) {
VS.applyValidatedStateToModel(validatedModelStates.get(
0
), model);
}
// TestModelChild
List<ValidatedModelState> validatedChildStates = validationResponse.getValidatedModelStatesForContext(
"TestModel"
,
"TestModelChild"
);
for
(
int
i =
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:public
class
ModuleContextListener
implements
com.onenetwork.platform.env.module.ModuleContextListener {
public
void
moduleInitialized(Module module)
throws
Exception {
registerBusinessRuleContext();
registerValidationRequestProvider();
}
private
void
registerValidationRequestProvider() {
final
ValidationService VS = Services.get(ValidationService.
class
);
final
UserContextService UCS = Services.get(UserContextService.
class
);
final
ModelDataService MDS = Services.get(ModelDataService.
class
);
ValidationRequestProviderRegistry.register(
new
ValidationRequestProvider() {
@Override
public
Optional<ValidationRequest> provide(ModelLevelType modelLevelType, Long sysId, String actionName, PlatformUserContext userContext) {
// Ignore models other than our TestModel
if
(!
"MDMT.TestModel"
.equals(modelLevelType.getValue())) {
return
Optional.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 =
new
ValidationRequest(packages);
TestModel testModel =
null
;
if
(sysId !=
null
) {
testModel = MDS.readById(TestModel.
class
, sysId, userContext);
}
// Bind values from TestModel
Map<String, Object> valueBindings = testModel !=
null
? VS.createModelBindings(testModel) :
new
HashMap<>();
validationRequest.bindSingle(
"TestModel"
,
"TestModel"
, valueBindings);
if
(testModel !=
null
) {
// Bind values from every TestModelChild
List<Map<String, Object>> childLevelValueBindings =
new
ArrayList<>();
for
(TestModelChild testModelChild : testModel.getTestModelChilds()) {
childLevelValueBindings.add(VS.createModelBindings(testModelChild));
}
validationRequest.bindMultiple(
"TestModel"
,
"TestModelChild"
, childLevelValueBindings);
}
return
Optional.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.