Documentation Index
Fetch the complete documentation index at: https://mintlify.com/idempiere/idempiere/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Factory Pattern in iDempiere allows you to:
- Extend or replace standard model classes with custom implementations
- Create custom process instances
- Provide custom callout implementations
- Implement plugin-based extensibility
Factories are registered as OSGi services and are invoked by the framework when models, processes, or callouts are instantiated.
Base Package: org.adempiere.base
Source Files:
org.adempiere.base/src/org/adempiere/base/IModelFactory.java
org.adempiere.base/src/org/adempiere/base/IProcessFactory.java
org.adempiere.base/src/org/adempiere/base/ICalloutFactory.java
IModelFactory
Provides custom model implementations for tables.
Package: org.adempiere.base
Interface Methods
getClass
public Class<?> getClass(String tableName)
Database table name (e.g., “C_Order”, “C_Invoice”)
Model class for the table, or null if not handled by this factory
Returns the persistence class to use for a given table.
getPO (by ID)
public PO getPO(String tableName, int Record_ID, String trxName)
Loaded PO instance, or null if not handled by this factory
getPO (by UUID)
default public PO getPO(String tableName, String Record_UU, String trxName)
Loaded PO instance, or null if not handled
If UUID constructor not implemented in this factory
getPO (from ResultSet)
public PO getPO(String tableName, ResultSet rs, String trxName)
ResultSet positioned at row to load
Loaded PO instance, or null if not handled by this factory
Implementation Example
package com.example.factory;
import java.sql.ResultSet;
import org.adempiere.base.IModelFactory;
import org.compiere.model.PO;
import org.compiere.util.Env;
import com.example.model.MCustomOrder;
import com.example.model.MCustomInvoice;
/**
* Custom Model Factory
* Provides custom implementations for specific tables
*/
public class CustomModelFactory implements IModelFactory {
@Override
public Class<?> getClass(String tableName) {
if (tableName.equals("C_Order")) {
return MCustomOrder.class;
}
else if (tableName.equals("C_Invoice")) {
return MCustomInvoice.class;
}
return null;
}
@Override
public PO getPO(String tableName, int Record_ID, String trxName) {
if (tableName.equals("C_Order")) {
return new MCustomOrder(Env.getCtx(), Record_ID, trxName);
}
else if (tableName.equals("C_Invoice")) {
return new MCustomInvoice(Env.getCtx(), Record_ID, trxName);
}
return null;
}
@Override
public PO getPO(String tableName, String Record_UU, String trxName) {
if (tableName.equals("C_Order")) {
return new MCustomOrder(Env.getCtx(), Record_UU, trxName);
}
else if (tableName.equals("C_Invoice")) {
return new MCustomInvoice(Env.getCtx(), Record_UU, trxName);
}
return null;
}
@Override
public PO getPO(String tableName, ResultSet rs, String trxName) {
if (tableName.equals("C_Order")) {
return new MCustomOrder(Env.getCtx(), rs, trxName);
}
else if (tableName.equals("C_Invoice")) {
return new MCustomInvoice(Env.getCtx(), rs, trxName);
}
return null;
}
}
OSGi Service Registration
Using Declarative Services (component.xml):
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="com.example.factory.CustomModelFactory">
<implementation class="com.example.factory.CustomModelFactory"/>
<service>
<provide interface="org.adempiere.base.IModelFactory"/>
</service>
<property name="service.ranking" type="Integer" value="100"/>
</scr:component>
Using Annotations:
import org.osgi.service.component.annotations.Component;
@Component(
name = "com.example.factory.CustomModelFactory",
service = IModelFactory.class,
property = {"service.ranking:Integer=100"}
)
public class CustomModelFactory implements IModelFactory {
// Implementation as above
}
IProcessFactory
Provides custom process instances.
Package: org.adempiere.base
Interface Methods
newProcessInstance
public ProcessCall newProcessInstance(String className)
Fully qualified class name of the process
New process instance, or null if not handled by this factory
Implementation Example
package com.example.factory;
import org.adempiere.base.IProcessFactory;
import org.compiere.process.ProcessCall;
import com.example.process.CustomOrderProcess;
import com.example.process.CustomInvoiceProcess;
/**
* Custom Process Factory
* Creates instances of custom processes
*/
public class CustomProcessFactory implements IProcessFactory {
@Override
public ProcessCall newProcessInstance(String className) {
if (className.equals("com.example.process.CustomOrderProcess")) {
return new CustomOrderProcess();
}
else if (className.equals("com.example.process.CustomInvoiceProcess")) {
return new CustomInvoiceProcess();
}
return null;
}
}
OSGi Service Registration
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="com.example.factory.CustomProcessFactory">
<implementation class="com.example.factory.CustomProcessFactory"/>
<service>
<provide interface="org.adempiere.base.IProcessFactory"/>
</service>
<property name="service.ranking" type="Integer" value="10"/>
</scr:component>
ICalloutFactory
Provides custom callout instances.
Package: org.adempiere.base
Interface Methods
getCallout
public Callout getCallout(String className, String methodName)
Fully qualified class name of the callout
Method name within the callout class
Callout instance, or null if not handled by this factory
Implementation Example
package com.example.factory;
import org.adempiere.base.ICalloutFactory;
import org.compiere.model.Callout;
import com.example.callout.CustomOrderCallout;
import com.example.callout.CustomInvoiceCallout;
/**
* Custom Callout Factory
* Creates instances of custom callouts
*/
public class CustomCalloutFactory implements ICalloutFactory {
@Override
public Callout getCallout(String className, String methodName) {
Callout callout = null;
if (className.equals("com.example.callout.CustomOrderCallout")) {
callout = new CustomOrderCallout();
}
else if (className.equals("com.example.callout.CustomInvoiceCallout")) {
callout = new CustomInvoiceCallout();
}
return callout;
}
}
OSGi Service Registration with Priority
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="com.example.factory.CustomCalloutFactory">
<implementation class="com.example.factory.CustomCalloutFactory"/>
<service>
<provide interface="org.adempiere.base.ICalloutFactory"/>
</service>
<property name="service.ranking" type="Integer" value="1"/>
</scr:component>
For callout factories, use service.ranking:Integer=1 or higher to prioritize your factory over the core factory.
Complete Plugin Example
A complete example showing factory integration in an OSGi plugin.
Project Structure
com.example.plugin/
├── META-INF/
│ └── MANIFEST.MF
├── OSGI-INF/
│ ├── ModelFactory.xml
│ ├── ProcessFactory.xml
│ └── CalloutFactory.xml
├── src/
│ └── com/example/
│ ├── factory/
│ │ ├── CustomModelFactory.java
│ │ ├── CustomProcessFactory.java
│ │ └── CustomCalloutFactory.java
│ ├── model/
│ │ └── MCustomOrder.java
│ ├── process/
│ │ └── CustomOrderProcess.java
│ └── callout/
│ └── CustomOrderCallout.java
└── build.properties
MANIFEST.MF
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Custom Plugin
Bundle-SymbolicName: com.example.plugin;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-17
Require-Bundle: org.adempiere.base;bundle-version="11.0.0"
Service-Component: OSGI-INF/ModelFactory.xml,
OSGI-INF/ProcessFactory.xml,
OSGI-INF/CalloutFactory.xml
Import-Package: org.osgi.service.component.annotations
Custom Model Implementation
package com.example.model;
import java.sql.ResultSet;
import java.util.Properties;
import org.compiere.model.MOrder;
import org.compiere.util.CLogger;
/**
* Custom Order Model
* Extends standard order with custom business logic
*/
public class MCustomOrder extends MOrder {
private static final long serialVersionUID = 1L;
private static CLogger log = CLogger.getCLogger(MCustomOrder.class);
public MCustomOrder(Properties ctx, int C_Order_ID, String trxName) {
super(ctx, C_Order_ID, trxName);
}
public MCustomOrder(Properties ctx, ResultSet rs, String trxName) {
super(ctx, rs, trxName);
}
@Override
protected boolean beforeSave(boolean newRecord) {
if (!super.beforeSave(newRecord))
return false;
// Custom validation
if (newRecord) {
log.info("Creating custom order with special logic");
// Add custom logic here
}
return true;
}
@Override
protected boolean afterSave(boolean newRecord, boolean success) {
if (!super.afterSave(newRecord, success))
return false;
if (newRecord) {
// Custom post-save logic
createCustomRecords();
}
return true;
}
private void createCustomRecords() {
// Custom implementation
log.info("Creating custom related records for order " + getDocumentNo());
}
}
Service Ranking
The service.ranking property determines the order in which factories are invoked:
- Higher values = Higher priority
- Core iDempiere factories typically use ranking of 0
- Plugin factories should use positive values to override core behavior
- Typical values:
1-10: Minor extensions
10-50: Standard plugin priority
50-100: High priority overrides
Example with multiple factories:
// High priority factory - checked first
@Component(
service = IModelFactory.class,
property = {"service.ranking:Integer=100"}
)
public class HighPriorityFactory implements IModelFactory {
// ...
}
// Standard priority factory - checked if high priority returns null
@Component(
service = IModelFactory.class,
property = {"service.ranking:Integer=10"}
)
public class StandardFactory implements IModelFactory {
// ...
}
Factory Chain
When multiple factories are registered:
- Framework sorts factories by
service.ranking (descending)
- Invokes each factory in order
- First factory to return non-null result wins
- If all factories return null, uses default implementation
Best Practices
- Return Null: Always return
null for tables/classes you don’t handle
- Performance: Keep factory methods lightweight - they’re called frequently
- Service Ranking: Use appropriate ranking to control factory precedence
- Consistency: Implement all three getPO methods consistently
- Testing: Test factory behavior in all three construction scenarios
- Documentation: Document which tables/processes your factory handles
- Namespace: Use unique package names to avoid class conflicts
- Singleton: Factories are singletons - avoid instance state
- Thread Safety: Ensure factory methods are thread-safe
- Logging: Log factory instantiations for debugging
Debugging Factories
Enable factory debugging in iDempiere:
// Add to your factory for debugging
import org.compiere.util.CLogger;
private static CLogger log = CLogger.getCLogger(CustomModelFactory.class);
@Override
public PO getPO(String tableName, int Record_ID, String trxName) {
if (tableName.equals("C_Order")) {
log.info("CustomModelFactory creating MCustomOrder for ID " + Record_ID);
return new MCustomOrder(Env.getCtx(), Record_ID, trxName);
}
return null;
}
See Also