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


Steps for manual integration

  1. Make the Business Rules Management UIs available to the user

    1. Permissions should be added for the models listed above for the role types that will be managing Business Rules.

    2. The Business Rules report is accessible through the WebAction provided by PLT:

      <WebActionRef name="ValidationRules" module="PLT" />
  2. Register the Business Rule Context

    1. 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());
      }
      }
  3. Add code to fetch and evaluate the Business Rules

    1. 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:

      1. Fetch the Business Rules from the database.

      2. Build a ValidationRequest using the list of ValidationRules.

      3. 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));
      }
      }
      }
    2. 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.