diff --git a/org.adempiere.base/.project b/org.adempiere.base/.project
index 1b2b28ba7c..ca3f65e8c2 100644
--- a/org.adempiere.base/.project
+++ b/org.adempiere.base/.project
@@ -25,6 +25,11 @@
+
+ org.eclipse.pde.ds.core.builder
+
+
+
org.eclipse.m2e.core.maven2Nature
diff --git a/org.adempiere.base/.settings/org.eclipse.pde.ds.annotations.prefs b/org.adempiere.base/.settings/org.eclipse.pde.ds.annotations.prefs
new file mode 100644
index 0000000000..73a356b6d0
--- /dev/null
+++ b/org.adempiere.base/.settings/org.eclipse.pde.ds.annotations.prefs
@@ -0,0 +1,8 @@
+classpath=true
+dsVersion=V1_3
+eclipse.preferences.version=1
+enabled=true
+generateBundleActivationPolicyLazy=true
+path=OSGI-INF
+validationErrorLevel=error
+validationErrorLevel.missingImplicitUnbindMethod=error
diff --git a/org.adempiere.base/META-INF/MANIFEST.MF b/org.adempiere.base/META-INF/MANIFEST.MF
index e73dc7663f..057766f538 100644
--- a/org.adempiere.base/META-INF/MANIFEST.MF
+++ b/org.adempiere.base/META-INF/MANIFEST.MF
@@ -90,6 +90,7 @@ Import-Package: com.google.zxing,
org.osgi.framework,
org.osgi.service.cm;version="1.3.0",
org.osgi.service.component;version="1.1.0",
+ org.osgi.service.component.annotations;version="1.3.0",
org.osgi.service.component.runtime;version="1.3.0",
org.osgi.service.component.runtime.dto;version="1.3.0",
org.osgi.service.event;version="1.2.0",
diff --git a/org.adempiere.base/OSGI-INF/org.idempiere.model.MappedModelFactory.xml b/org.adempiere.base/OSGI-INF/org.idempiere.model.MappedModelFactory.xml
new file mode 100644
index 0000000000..3f7496474e
--- /dev/null
+++ b/org.adempiere.base/OSGI-INF/org.idempiere.model.MappedModelFactory.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.adempiere.base/src/org/adempiere/base/Core.java b/org.adempiere.base/src/org/adempiere/base/Core.java
index 0545279038..6f8c259ade 100644
--- a/org.adempiere.base/src/org/adempiere/base/Core.java
+++ b/org.adempiere.base/src/org/adempiere/base/Core.java
@@ -55,6 +55,7 @@ import org.idempiere.distributed.IMessageService;
import org.idempiere.fa.service.api.DepreciationFactoryLookupDTO;
import org.idempiere.fa.service.api.IDepreciationMethod;
import org.idempiere.fa.service.api.IDepreciationMethodFactory;
+import org.idempiere.model.IMappedModelFactory;
/**
* This is a facade class for the Service Locator.
@@ -923,5 +924,25 @@ public class Core {
}
return ids;
}
-
+
+ private static IServiceReferenceHolder s_mappedModelFactoryReference = null;
+
+ /**
+ *
+ * @return {@link IMappedModelFactory}
+ */
+ public static IMappedModelFactory getMappedModelFactory(){
+ IMappedModelFactory modelFactoryService = null;
+ if (s_mappedModelFactoryReference != null) {
+ modelFactoryService = s_mappedModelFactoryReference.getService();
+ if (modelFactoryService != null)
+ return modelFactoryService;
+ }
+ IServiceReferenceHolder serviceReference = Service.locator().locate(IMappedModelFactory.class).getServiceReference();
+ if (serviceReference != null) {
+ modelFactoryService = serviceReference.getService();
+ s_mappedModelFactoryReference = serviceReference;
+ }
+ return modelFactoryService;
+ }
}
diff --git a/org.adempiere.base/src/org/idempiere/model/IMappedModelFactory.java b/org.adempiere.base/src/org/idempiere/model/IMappedModelFactory.java
new file mode 100644
index 0000000000..d5623a1b94
--- /dev/null
+++ b/org.adempiere.base/src/org/idempiere/model/IMappedModelFactory.java
@@ -0,0 +1,57 @@
+/***********************************************************************
+ * This file is part of iDempiere ERP Open Source *
+ * http://www.idempiere.org *
+ * *
+ * Copyright (C) Contributors *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License *
+ * as published by the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software *
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
+ * MA 02110-1301, USA. *
+ * *
+ * Contributors: *
+ * - hengsin *
+ **********************************************************************/
+package org.idempiere.model;
+
+import java.sql.ResultSet;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+import org.compiere.model.PO;
+
+/**
+ *
+ * @author hengsin
+ *
+ */
+public interface IMappedModelFactory {
+
+ /**
+ * add table name to class mapping
+ * @param tableName
+ * @param classSupplier
+ * @param recordIdFunction
+ * @param resultSetFunction
+ */
+ void addMapping(String tableName, Supplier> classSupplier,
+ BiFunction recordIdFunction,
+ BiFunction resultSetFunction);
+
+ /**
+ * remove table name to class mapping
+ * @param tableName
+ */
+ void removeMapping(String tableName);
+
+}
\ No newline at end of file
diff --git a/org.adempiere.base/src/org/idempiere/model/MappedModelFactory.java b/org.adempiere.base/src/org/idempiere/model/MappedModelFactory.java
new file mode 100644
index 0000000000..6f36bd1505
--- /dev/null
+++ b/org.adempiere.base/src/org/idempiere/model/MappedModelFactory.java
@@ -0,0 +1,88 @@
+/***********************************************************************
+ * This file is part of iDempiere ERP Open Source *
+ * http://www.idempiere.org *
+ * *
+ * Copyright (C) Contributors *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License *
+ * as published by the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software *
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
+ * MA 02110-1301, USA. *
+ * *
+ * Contributors: *
+ * - hengsin *
+ **********************************************************************/
+package org.idempiere.model;
+
+import java.sql.ResultSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+import org.adempiere.base.IModelFactory;
+import org.compiere.model.PO;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * @author hengsin
+ *
+ */
+@Component(name = "org.idempiere.model.MappedModelFactory",
+ immediate = true,
+ service = {IModelFactory.class, IMappedModelFactory.class},
+ property = {"service.ranking:Integer=1"})
+public class MappedModelFactory implements IModelFactory, IMappedModelFactory {
+
+ private final ConcurrentHashMap>> classMap = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> recordIdMap = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> resultSetMap = new ConcurrentHashMap<>();
+
+ /**
+ * default constructor
+ */
+ public MappedModelFactory() {
+ }
+
+ @Override
+ public Class> getClass(String tableName) {
+ var supplier = classMap.get(tableName);
+ return supplier != null ? supplier.get() : null;
+ }
+
+ @Override
+ public PO getPO(String tableName, int Record_ID, String trxName) {
+ var function = recordIdMap.get(tableName);
+ return function != null ? function.apply(Record_ID, trxName) : null;
+ }
+
+ @Override
+ public PO getPO(String tableName, ResultSet rs, String trxName) {
+ var function = resultSetMap.get(tableName);
+ return function != null ? function.apply(rs, trxName) : null;
+ }
+
+ @Override
+ public void addMapping(String tableName, Supplier> classSupplier, BiFunction recordIdFunction,
+ BiFunction resultSetFunction) {
+ classMap.put(tableName, classSupplier);
+ recordIdMap.put(tableName, recordIdFunction);
+ resultSetMap.put(tableName, resultSetFunction);
+ }
+
+ @Override
+ public void removeMapping(String tableName) {
+ classMap.remove(tableName);
+ recordIdMap.remove(tableName);
+ resultSetMap.remove(tableName);
+ }
+}
diff --git a/org.idempiere.test/src/org/idempiere/test/model/MappedModelFactoryTest.java b/org.idempiere.test/src/org/idempiere/test/model/MappedModelFactoryTest.java
new file mode 100644
index 0000000000..7541d299c6
--- /dev/null
+++ b/org.idempiere.test/src/org/idempiere/test/model/MappedModelFactoryTest.java
@@ -0,0 +1,126 @@
+/***********************************************************************
+ * This file is part of iDempiere ERP Open Source *
+ * http://www.idempiere.org *
+ * *
+ * Copyright (C) Contributors *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License *
+ * as published by the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software *
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
+ * MA 02110-1301, USA. *
+ * *
+ * Contributors: *
+ * - hengsin *
+ **********************************************************************/
+package org.idempiere.test.model;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.sql.ResultSet;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Properties;
+
+import org.adempiere.base.Core;
+import org.adempiere.base.IModelFactory;
+import org.compiere.model.MTable;
+import org.compiere.model.PO;
+import org.compiere.model.X_Test;
+import org.compiere.util.CacheMgt;
+import org.compiere.util.Env;
+import org.idempiere.model.IMappedModelFactory;
+import org.idempiere.model.MappedModelFactory;
+import org.idempiere.test.AbstractTestCase;
+import org.idempiere.test.TestActivator;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.osgi.framework.BundleContext;
+
+/**
+ * @author hengsin
+ *
+ */
+@TestMethodOrder(OrderAnnotation.class)
+public class MappedModelFactoryTest extends AbstractTestCase {
+
+ /**
+ *
+ */
+ public MappedModelFactoryTest() {
+ }
+
+ @Test
+ @Order(1)
+ public void testDefaultMappedModelFactory() {
+ IMappedModelFactory mappedFactory = Core.getMappedModelFactory();
+ mappedFactory.addMapping(MyTest.Table_Name, () -> MyTest.class, (id, trxName) -> new MyTest(Env.getCtx(), id, trxName),
+ (rs, trxName) -> new MyTest(Env.getCtx(), rs, trxName));
+ PO po = MTable.get(MyTest.Table_ID).getPO(0, getTrxName());
+ assertTrue(po instanceof MyTest, "PO not instanceof MyTest. PO.className="+po.getClass().getName());
+ }
+
+ @Test
+ @Order(2)
+ public void testCustomMappedModelFactory() {
+ BundleContext bc = TestActivator.context;
+ Dictionary properties = new Hashtable();
+ properties.put("service.ranking", Integer.valueOf(2));
+ bc.registerService(IModelFactory.class, new MyFactory(), properties);
+ CacheMgt.get().reset();
+ PO po = MTable.get(MyTest2.Table_ID).getPO(0, getTrxName());
+ assertTrue(po instanceof MyTest2, "PO not instanceof MyTest2. PO.className="+po.getClass().getName());
+ }
+
+ private final static class MyFactory extends MappedModelFactory {
+
+ public MyFactory() {
+ addMapping(MyTest2.Table_Name, () -> MyTest2.class, (id, trxName) -> new MyTest2(Env.getCtx(), id, trxName),
+ (rs, trxName) -> new MyTest2(Env.getCtx(), rs, trxName));
+ }
+
+ }
+
+ private final static class MyTest extends X_Test {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 2010413233032792416L;
+
+ public MyTest(Properties ctx, int Test_ID, String trxName) {
+ super(ctx, Test_ID, trxName);
+ }
+
+ public MyTest(Properties ctx, ResultSet rs, String trxName) {
+ super(ctx, rs, trxName);
+ }
+ }
+
+ private final static class MyTest2 extends X_Test {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 2010413233032792416L;
+
+ public MyTest2(Properties ctx, int Test_ID, String trxName) {
+ super(ctx, Test_ID, trxName);
+ }
+
+ public MyTest2(Properties ctx, ResultSet rs, String trxName) {
+ super(ctx, rs, trxName);
+ }
+ }
+}