Skip to main content

Developing Plug-in Model Validators

๐ŸŽฏ Goalโ€‹

This tutorial shows you how to develop model validators in your own plug-in, enabling you to add custom validation and business logic that executes during model operations.

What is a Model Validator?โ€‹

A model validator is a component that intercepts and validates model (record) operations in iDempiere. Model validators are similar to event handlers but use a different registration mechanism. They are commonly used to:

  • Validate data before records are saved or deleted
  • Enforce complex business rules across multiple fields
  • Calculate and set field values based on business logic
  • Prevent invalid operations by throwing exceptions
  • Audit and log specific model changes
  • Trigger side effects when records change
Model Validators vs Event Handlers

Model validators and event handlers serve similar purposes. The main difference is in how they're registered and managed:

  • Model Validators use the ModelValidator interface and IModelValidatorFactory
  • Event Handlers use the OSGi Event Admin framework and IEventManager

For new development, consider using Event Handlers as they provide more flexibility and better OSGi integration.


โœ… Prerequisitesโ€‹

Before starting, review:

You should already know how to create a plug-in, as this guide will not cover that in detail.


๐ŸŽฅ Video Tutorialโ€‹

Watch this video walkthrough:
https://www.youtube.com/watch?v=Dwjl8p1Xguw


๐Ÿ“‹ Creating a Model Validatorโ€‹

Step 1: Create the Model Validator Classโ€‹

Create a new class that implements the ModelValidator interface and all its required methods.

Example: MyModelValidator.java

package com.mycompany.validator;

import java.util.logging.Level;
import org.compiere.model.ModelValidator;
import org.compiere.model.PO;
import org.compiere.model.MClient;
import org.compiere.model.MOrder;
import org.compiere.util.CLogger;

public class MyModelValidator implements ModelValidator {

/** Logger */
private static CLogger log = CLogger.getCLogger(MyModelValidator.class);

/** Client ID */
private int m_AD_Client_ID = -1;

/** User ID */
private int m_AD_User_ID = -1;

/** Organization ID */
private int m_AD_Org_ID = -1;

/** Role ID */
private int m_AD_Role_ID = -1;

@Override
public void initialize(ModelValidationEngine engine, MClient client) {
// Store client ID
if (client != null)
m_AD_Client_ID = client.getAD_Client_ID();

// Register for model changes on specific tables
engine.addModelChange(MOrder.Table_Name, this);

log.info("Model Validator initialized for client: " + m_AD_Client_ID);
}

@Override
public int getAD_Client_ID() {
return m_AD_Client_ID;
}

@Override
public String login(int AD_Org_ID, int AD_Role_ID, int AD_User_ID) {
// Store login information for later use
m_AD_Org_ID = AD_Org_ID;
m_AD_Role_ID = AD_Role_ID;
m_AD_User_ID = AD_User_ID;

log.info("Login - Org: " + AD_Org_ID + ", Role: " + AD_Role_ID +
", User: " + AD_User_ID);

return null; // Return null if login is successful
}

@Override
public String modelChange(PO po, int type) throws Exception {
// Log the model change
log.info("Model Change - Table: " + po.get_TableName() +
", Type: " + type + ", ID: " + po.get_ID());

// Handle specific validation based on type
if (po instanceof MOrder && type == TYPE_BEFORE_NEW) {
MOrder order = (MOrder) po;

// Example validation: Check if Business Partner is set
if (order.getC_BPartner_ID() <= 0) {
return "Business Partner is required";
}

// Example calculation: Set a custom field
// order.set_ValueOfColumn("CustomField", calculatedValue);
}

return null; // Return null if validation passes
}

@Override
public String docValidate(PO po, int timing) {
// Handle document-level validations
log.info("Document Validate - Table: " + po.get_TableName() +
", Timing: " + timing);

// You can add document-specific validation here

return null; // Return null if validation passes
}
}

Key Methods Explainedโ€‹

  • initialize() - Called when the validator is loaded. Register for table events here.
  • getAD_Client_ID() - Returns the client ID this validator serves.
  • login() - Called when a user logs in. Store context information here.
  • modelChange() - Called during model operations. Add validation logic here.
  • docValidate() - Called during document workflow operations.

Validation Typesโ€‹

The type parameter in modelChange() can be:

  • TYPE_BEFORE_NEW - Before a new record is created
  • TYPE_AFTER_NEW - After a new record is created
  • TYPE_BEFORE_CHANGE - Before a record is updated
  • TYPE_AFTER_CHANGE - After a record is updated
  • TYPE_BEFORE_DELETE - Before a record is deleted
  • TYPE_AFTER_DELETE - After a record is deleted
Return Values
  • Return null if validation passes
  • Return an error message string to prevent the operation and show the message to the user
  • Throw an exception to halt the operation with an error

๐Ÿ”ง Registering Your Model Validatorโ€‹

Using a Model Validator Factoryโ€‹

Create a factory class that implements IModelValidatorFactory to tell iDempiere how to find your validator.

Step 1: Create the Factory Classโ€‹

Example: MyModelValidatorFactory.java

package com.mycompany.validator;

import org.adempiere.base.IModelValidatorFactory;
import org.compiere.model.ModelValidator;

public class MyModelValidatorFactory implements IModelValidatorFactory {

@Override
public ModelValidator newModelValidatorInstance(String className) {
if (className.equals("com.mycompany.validator.MyModelValidator")) {
return new MyModelValidator();
}
return null;
}
}

Step 2: Register as OSGi Componentโ€‹

Option A: Using Annotations (Recommended)

Add the @Component annotation to your factory class:

import org.osgi.service.component.annotations.Component;

@Component(
service = IModelValidatorFactory.class,
immediate = true,
property = {"service.ranking:Integer=100"}
)
public class MyModelValidatorFactory implements IModelValidatorFactory {
// ... implementation
}

See Event Handling with Annotations for more details.

Option B: Manual Component Definition

  1. Create a directory named OSGI-INF in your project root
  2. Create a file OSGI-INF/modelvalidatorfactory.xml:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="com.mycompany.validator.modelvalidatorfactory">
<implementation class="com.mycompany.validator.MyModelValidatorFactory"/>
<property name="service.ranking" type="Integer" value="100"/>
<service>
<provide interface="org.adempiere.base.IModelValidatorFactory"/>
</service>
</scr:component>
  1. Update your META-INF/MANIFEST.MF:
Service-Component: OSGI-INF/*.xml
  1. Ensure build.properties includes the OSGI-INF directory:
bin.includes = META-INF/,\
.,\
OSGI-INF/
Service Ranking

The service.ranking property determines the priority for OSGi service lookup. Set it to a value like 100 to ensure your factory is loaded before the default iDempiere model validator factory.


โš™๏ธ Configuring Your Model Validator in iDempiereโ€‹

Creating a Model Validator Recordโ€‹

  1. Log in as System Administrator
  2. Open System Admin > General Rules > Model Validator
  3. Create a new record:
    • Name: Your validator name (e.g., "My Custom Validator")
    • Entity Type: Your custom entity type
    • Model Validation Class: Fully qualified class name
      (e.g., com.mycompany.validator.MyModelValidator)
    • Description: Brief description of what the validator does

The validator will be automatically activated when the system starts.


๐Ÿงช Testing Your Model Validatorโ€‹

  1. Deploy your plug-in to iDempiere
  2. Ensure your plug-in is set to auto-start in the Run Configuration
  3. Start the iDempiere server
  4. Create or update a record that should trigger your validator (e.g., a Sales Order)
  5. Check the console logs for validation messages
  6. Verify that validation rules are enforced
Debugging

Add logging statements in your validator methods to trace execution:

log.info("Validating order: " + order.getDocumentNo());
log.warning("Validation failed: Missing required field");

๐Ÿ”„ Model Validator vs Event Handlerโ€‹

Both approaches serve similar purposes but have different characteristics:

FeatureModel ValidatorEvent Handler
RegistrationVia IModelValidatorFactoryVia OSGi Event Admin
InterfaceModelValidator interfaceEventHandler interface or AbstractEventHandler
FlexibilityLess flexibleMore flexible with topics
RecommendedLegacy codeNew development
DocumentationOlder APIModern OSGi approach
Recommendation

For new plug-in development, consider using Event Handlers instead, as they provide better OSGi integration and more flexibility. Model validators are still supported but are considered the legacy approach.