Using IModelFactory in Plug-ins
This guide explains how to use the IModelFactory service in your plug-in project to provide custom model classes for your tables.
π― Goalβ
The IModelFactory service allows you to provide your own model classes for custom tables. When properly configured, iDempiere will:
- Automatically use your custom models throughout the application
- Call your model's lifecycle methods (e.g.,
beforeSave(),afterSave()) - Return your model class when using the Query API (instead of
GenericPO)
π Understanding Model Classesβ
Class Naming Conventions: X, I, and Mβ
iDempiere uses different classes for its models, all inheriting from the PO (Persistent Object) base class.
The I_ Interfaceβ
The I_ class is an interface that contains:
- A constant for the table name
- The table's ID in the Application Dictionary (loaded dynamically via
MTable.getTable_ID()) - Constants for every column in the table
- Template declarations for all getter and setter methods
The X_ Base Modelβ
The X_ class is the basic model for every table:
- Extends
POand implements the correspondingI_interface - Implements all getters and setters with validation logic
- Uses PO methods to persist data
- Auto-generated: Will be regenerated when you run the model generator
Never put custom business logic in the X_ class, as it will be overwritten when you regenerate your models!
The M Custom Modelβ
The M class is where your custom business logic lives.
Naming convention: M + table prefix (if β₯3 chars) + table name (camelCase, no spaces/underscores)
Example: For table EVE_This_Is_My_Table, create class MEVEThisIsMyTable
Using your 3-letter table prefix (e.g., "EVE") makes it easier to identify custom models at a glance.
What you can do in the M class:
- Override model hooks:
beforeSave(),afterSave(),beforeDelete(),afterDelete() - Implement the
DocActioninterface to make your table behave like a document - Create special getters and setters (like
MOrder.getLines())
π οΈ Implementing a Model Factoryβ
What You Needβ
- A Model class (usually extending an existing model or from a custom table)
- A ModelFactory class (implements
IModelFactory)
Your model factory and model class must be in different classes. Don't implement IModelFactory in your model class itself!
The good news: You only need one model factory for all your custom models.
Example: Custom MOrder Subclassβ
First, create your custom model class MOrder_New that extends MOrder:
public class MOrder_New extends MOrder {
// Default constructors
public MOrder_New(Properties ctx, int C_Order_ID, String trxName) {
super(ctx, C_Order_ID, trxName);
}
public MOrder_New(Properties ctx, ResultSet rs, String trxName) {
super(ctx, rs, trxName);
}
@Override
protected boolean beforeSave(boolean newRecord) {
// Your custom logic here
System.out.println("Custom beforeSave called!");
return super.beforeSave(newRecord);
}
}
Creating the Model Factoryβ
Create a class that implements IModelFactory and register it using OSGi annotations:
import org.adempiere.base.IModelFactory;
import org.compiere.model.PO;
import org.compiere.util.Env;
import org.osgi.service.component.annotations.Component;
@Component(
property = {"service.ranking:Integer=100"},
service = org.adempiere.base.IModelFactory.class
)
public class MyModelFactory extends AbstractModelFactory {
@Override
public Class<?> getClass(String tableName) {
if (MOrder.Table_Name.equals(tableName))
return MOrder_New.class;
return null;
}
@Override
public PO getPO(String tableName, int Record_ID, String trxName) {
if (MOrder.Table_Name.equals(tableName))
return new MOrder_New(Env.getCtx(), Record_ID, trxName);
return null;
}
@Override
public PO getPO(String tableName, ResultSet rs, String trxName) {
if (MOrder.Table_Name.equals(tableName))
return new MOrder_New(Env.getCtx(), rs, trxName);
return null;
}
}
Always extend AbstractModelFactory as it handles most common tasks for you. This class has been part of iDempiere since version 9.
Testing Your Model Factoryβ
- Make sure your plug-in is active in the run configuration
- Start the client and verify your plug-in is loaded
- Log in as GardenAdmin/GardenWorld
- Create a new Sales Order, fill in mandatory fields, and save
- Check the console log. You should see output from your custom model class
π Modern Approach: AnnotationBasedModelFactory (iDempiere 9+)β
Since iDempiere 9, there's a new default model factory that automatically detects model classes using annotations.
Benefitsβ
- Automatically detects models with the
@Modelannotation - Model classes with
Mprefix don't need annotation if theirX_classes are annotated - The default model generator automatically annotates
X_classes
For detailed information, see NF9_OSGi_New_Model_Factory and the AnnotationBasedModelFactory source code.
πΊοΈ Alternative: Map-Based Factoryβ
With a map-based factory, you declare associations using method references:
package org.evenos.factories;
import org.idempiere.model.MappedModelFactory;
import org.compiere.util.Env;
public class MyModelFactory extends MappedModelFactory {
public MyModelFactory() {
// Map each table to its model class and constructors
addMapping(MEVESub.Table_Name,
() -> MEVESub.class,
(id, trxName) -> new MEVESub(Env.getCtx(), id, trxName),
(rs, trxName) -> new MEVESub(Env.getCtx(), rs, trxName)
);
addMapping(MEVEMain.Table_Name,
() -> MEVEMain.class,
(id, trxName) -> new MEVEMain(Env.getCtx(), id, trxName),
(rs, trxName) -> new MEVEMain(Env.getCtx(), rs, trxName)
);
}
}
This approach provides a clean, declarative way to register multiple models.
π§ Generating Models for Custom Tablesβ
If you're creating models for your own custom tables:
Step 1: Create the Tableβ
Follow the instructions in Creating Windows to create your custom table in the Application Dictionary.
Custom tables should have a 3-letter prefix. Use camelCase for the rest of the name.
Example: CUST_This_Is_My_Table
Step 2: Generate Model Classesβ
- Log in as System
- Open the Generate Model process
- Fill in the parameters:
| Parameter | Description | Example |
|---|---|---|
| Folder | Where generated files will be stored | /home/user/gitidempiere/plugins/com.company.plugin/src/com/company/model/ |
| Table Like | Table name pattern (with wildcards) | CUST_% |
| Entity Type | Your entity type | U (User maintained) |
| Checkboxes | Check both to generate I and X classes | β Both checked |
Make sure you use the correct upper/lower case when specifying the table name!
- Click OK to generate the
I_andX_classes - Create your
Mclass by extending the generatedX_class
β Troubleshootingβ
If your model factory isn't working:
- β
Check service ranking: Make sure no other model factory has a higher
service.rankingproperty - β Separate classes: Verify your model and factory are in different classes
- β Plug-in activation: Ensure your plug-in is activated before you log in
- β
MANIFEST.MF: Verify it contains
Service-Component:with the path to your factory XML (e.g.,Service-Component: mymodelfactory.xml) - β Console logs: Check for any OSGi service registration errors
πΉ Video Tutorialsβ
For visual learners, check out these video explanations: