diff --git a/org.adempiere.base.callout/META-INF/MANIFEST.MF b/org.adempiere.base.callout/META-INF/MANIFEST.MF
index 580c187484..9785d4bf91 100644
--- a/org.adempiere.base.callout/META-INF/MANIFEST.MF
+++ b/org.adempiere.base.callout/META-INF/MANIFEST.MF
@@ -13,3 +13,4 @@ Export-Package: org.compiere.model
Bundle-ClassPath: .
Automatic-Module-Name: org.adempiere.base.callout
Bundle-Vendor: iDempiere Community
+Import-Package: org.osgi.service.component.annotations;version="1.3.0"
diff --git a/org.adempiere.base.callout/OSGI-INF/costadjustmentcalloutfactory.xml b/org.adempiere.base.callout/OSGI-INF/costadjustmentcalloutfactory.xml
deleted file mode 100644
index 1638494ee1..0000000000
--- a/org.adempiere.base.callout/OSGI-INF/costadjustmentcalloutfactory.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/org.adempiere.base.callout/OSGI-INF/org.adempiere.base.callout.factory.AnnotationBasedColumnCalloutFactoryImpl.xml b/org.adempiere.base.callout/OSGI-INF/org.adempiere.base.callout.factory.AnnotationBasedColumnCalloutFactoryImpl.xml
new file mode 100644
index 0000000000..e7391656db
--- /dev/null
+++ b/org.adempiere.base.callout/OSGI-INF/org.adempiere.base.callout.factory.AnnotationBasedColumnCalloutFactoryImpl.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.adempiere.base.callout/build.properties b/org.adempiere.base.callout/build.properties
index b5151e0f23..24af2f8751 100644
--- a/org.adempiere.base.callout/build.properties
+++ b/org.adempiere.base.callout/build.properties
@@ -1,5 +1,4 @@
bin.includes = META-INF/,\
- OSGI-INF/costadjustmentcalloutfactory.xml,\
OSGI-INF/,\
.
output.. = target/classes/
diff --git a/org.adempiere.base.callout/src/org/adempiere/base/callout/BPartnerCalloutFactory.java b/org.adempiere.base.callout/src/org/adempiere/base/callout/BPartnerCalloutFactory.java
deleted file mode 100644
index e49e88f217..0000000000
--- a/org.adempiere.base.callout/src/org/adempiere/base/callout/BPartnerCalloutFactory.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/******************************************************************************
- * Copyright (C) 2013 Diego Ruiz *
- * Copyright (C) 2013 Trek Global *
- * This program is free software; you can redistribute it and/or modify it *
- * under the terms version 2 of the GNU General Public License as published *
- * by the Free Software Foundation. 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., *
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
- *****************************************************************************/
-package org.adempiere.base.callout;
-
-import org.adempiere.base.IColumnCallout;
-import org.adempiere.base.IColumnCalloutFactory;
-import org.adempiere.model.CalloutBPartnerQuickEntry;
-import org.compiere.model.MBPartner;
-
-public class BPartnerCalloutFactory implements IColumnCalloutFactory {
-
- @Override
- public IColumnCallout[] getColumnCallouts(String tableName, String columnName) {
- if (tableName.equalsIgnoreCase(MBPartner.Table_Name)) {
- return new IColumnCallout[]{new CalloutBPartnerQuickEntry()};
- }
- return null;
- }
-}
diff --git a/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentCalloutFactory.java b/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentCalloutFactory.java
deleted file mode 100644
index 37f4daf7e2..0000000000
--- a/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentCalloutFactory.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/******************************************************************************
- * Copyright (C) 2013 Heng Sin Low *
- * Copyright (C) 2013 Trek Global *
- * This program is free software; you can redistribute it and/or modify it *
- * under the terms version 2 of the GNU General Public License as published *
- * by the Free Software Foundation. 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., *
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
- *****************************************************************************/
-package org.adempiere.base.callout;
-
-import java.math.BigDecimal;
-import java.util.Properties;
-
-import org.adempiere.base.IColumnCallout;
-import org.adempiere.base.IColumnCalloutFactory;
-import org.compiere.model.GridField;
-import org.compiere.model.GridTab;
-import org.compiere.model.GridTable;
-import org.compiere.model.I_M_InventoryLine;
-import org.compiere.model.MAcctSchema;
-import org.compiere.model.MClient;
-import org.compiere.model.MCost;
-import org.compiere.model.MCostElement;
-import org.compiere.model.MDocType;
-import org.compiere.model.MInventory;
-import org.compiere.model.MProduct;
-import org.compiere.util.Env;
-import org.compiere.util.Msg;
-
-/**
- * @author hengsin
- *
- */
-public class CostAdjustmentCalloutFactory implements IColumnCalloutFactory {
-
- /**
- *
- */
- public CostAdjustmentCalloutFactory() {
- }
-
- /* (non-Javadoc)
- * @see org.adempiere.base.IColumnCalloutFactory#getColumnCallouts(java.lang.String, java.lang.String)
- */
- @Override
- public IColumnCallout[] getColumnCallouts(String tableName,
- String columnName) {
- if (tableName.equalsIgnoreCase(I_M_InventoryLine.Table_Name)) {
- if (columnName.equalsIgnoreCase(I_M_InventoryLine.COLUMNNAME_M_Product_ID))
- return new IColumnCallout[]{new CostAdjustmentLineProduct()};
- else if (columnName.equalsIgnoreCase(I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID))
- return new IColumnCallout[]{new CostAdjustmentLineASI()};
- }
-
- return null;
- }
-
- /**
- * callout for m_product_id
- */
- private static class CostAdjustmentLineProduct implements IColumnCallout {
- @Override
- public String start(Properties ctx, int WindowNo, GridTab mTab,
- GridField mField, Object value, Object oldValue) {
- String trxName = null;
- if ( mTab != null
- && mTab.getTableModel() != null) {
- GridTable gt = mTab.getTableModel();
- if (gt.isImporting()) {
- trxName = gt.get_TrxName();
- }
- }
- MInventory inventory = new MInventory(ctx, (Integer) mTab.getValue("M_Inventory_ID"), trxName);
- if (MDocType.DOCSUBTYPEINV_CostAdjustment.equals(inventory.getC_DocType().getDocSubTypeInv())) {
- String costingMethod = inventory.getCostingMethod();
- if (value == null) {
- mTab.setValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice, BigDecimal.ZERO);
- mTab.setValue(I_M_InventoryLine.COLUMNNAME_NewCostPrice, BigDecimal.ZERO);
- } else {
- MProduct product = MProduct.get(ctx, (Integer) value);
- MClient client = MClient.get(ctx);
- MAcctSchema as = client.getAcctSchema();
-
- String costingLevel = product.getCostingLevel(as);
- if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(costingLevel)) {
- mTab.setValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice, BigDecimal.ZERO);
- mTab.setValue(I_M_InventoryLine.COLUMNNAME_NewCostPrice, BigDecimal.ZERO);
- }else {
- Object asiValue = mTab.getValue(I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
- int M_ASI_ID = asiValue != null ? (Integer)asiValue : 0;
- int AD_Org_ID = inventory.getAD_Org_ID();
- int C_Currency_ID = inventory.getC_Currency_ID();
-
- if (as.getC_Currency_ID() != C_Currency_ID)
- {
- MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(ctx, client.get_ID());
- for (int i = 0; i < ass.length ; i ++)
- {
- MAcctSchema a = ass[i];
- if (a.getC_Currency_ID() == C_Currency_ID)
- as = a ;
- }
- }
- MCost cost = product.getCostingRecord(as, AD_Org_ID, M_ASI_ID, costingMethod);
- if (cost == null) {
- if (!MCostElement.COSTINGMETHOD_StandardCosting.equals(costingMethod)) {
- mTab.setValue(mField, null);
- return Msg.getMsg(Env.getCtx(), "NoCostingRecord");
- }
- }
-
- mTab.setValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice, cost.getCurrentCostPrice());
- mTab.setValue(I_M_InventoryLine.COLUMNNAME_NewCostPrice, cost.getCurrentCostPrice());
- }
- }
- }
- return null;
- }
- }
-
- /**
- * callout for m_attributesetinstance_id
- */
- private class CostAdjustmentLineASI implements IColumnCallout {
- @Override
- public String start(Properties ctx, int WindowNo, GridTab mTab,
- GridField mField, Object value, Object oldValue) {
- String trxName = null;
- if ( mTab != null
- && mTab.getTableModel() != null) {
- GridTable gt = mTab.getTableModel();
- if (gt.isImporting()) {
- trxName = gt.get_TrxName();
- }
- }
- MInventory inventory = new MInventory(ctx, (Integer) mTab.getValue("M_Inventory_ID"), trxName);
- if (MDocType.DOCSUBTYPEINV_CostAdjustment.equals(inventory.getC_DocType().getDocSubTypeInv())) {
- String costingMethod = inventory.getCostingMethod();
- Object productValue = mTab.getValue(I_M_InventoryLine.COLUMNNAME_M_Product_ID);
- if (productValue == null || ((Integer)productValue).intValue() == 0) return null;
-
- MProduct product = MProduct.get(ctx, (Integer)productValue);
- int M_ASI_ID = value != null ? (Integer)value : 0;
- int AD_Org_ID = inventory.getAD_Org_ID();
- int C_Currency_ID = inventory.getC_Currency_ID();
-
- MClient client = MClient.get(ctx);
- MAcctSchema as = client.getAcctSchema();
-
- if (as.getC_Currency_ID() != C_Currency_ID)
- {
- MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(ctx, client.get_ID());
- for (int i = 0; i < ass.length ; i ++)
- {
- MAcctSchema a = ass[i];
- if (a.getC_Currency_ID() == C_Currency_ID)
- as = a ;
- }
- }
- MCost cost = product.getCostingRecord(as, AD_Org_ID, M_ASI_ID, costingMethod);
- if (cost == null) {
- if (!MCostElement.COSTINGMETHOD_StandardCosting.equals(costingMethod)) {
- mTab.setValue(mField, null);
- return Msg.getMsg(Env.getCtx(), "NoCostingRecord");
- }
- }
-
- if (cost != null) {
- BigDecimal currentCost = (BigDecimal) mTab.getValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice);
- if (currentCost == null || currentCost.compareTo(cost.getCurrentCostPrice())==0) return null;
-
- mTab.setValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice, cost.getCurrentCostPrice());
- mTab.setValue(I_M_InventoryLine.COLUMNNAME_NewCostPrice, cost.getCurrentCostPrice());
- }
-
- }
- return null;
- }
-
- }
-}
diff --git a/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentLineASI.java b/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentLineASI.java
new file mode 100644
index 0000000000..9fa44b07f8
--- /dev/null
+++ b/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentLineASI.java
@@ -0,0 +1,108 @@
+/***********************************************************************
+ * 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.adempiere.base.callout;
+
+import java.math.BigDecimal;
+import java.util.Properties;
+
+import org.adempiere.base.IColumnCallout;
+import org.adempiere.base.annotation.Callout;
+import org.compiere.model.GridField;
+import org.compiere.model.GridTab;
+import org.compiere.model.GridTable;
+import org.compiere.model.I_M_InventoryLine;
+import org.compiere.model.MAcctSchema;
+import org.compiere.model.MClient;
+import org.compiere.model.MCost;
+import org.compiere.model.MCostElement;
+import org.compiere.model.MDocType;
+import org.compiere.model.MInventory;
+import org.compiere.model.MProduct;
+import org.compiere.util.Env;
+import org.compiere.util.Msg;
+
+/**
+ *
+ * @author hengsin
+ *
+ */
+@Callout(tableName = I_M_InventoryLine.Table_Name, columnName = I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID)
+public class CostAdjustmentLineASI implements IColumnCallout {
+ @Override
+ public String start(Properties ctx, int WindowNo, GridTab mTab,
+ GridField mField, Object value, Object oldValue) {
+ String trxName = null;
+ if ( mTab != null
+ && mTab.getTableModel() != null) {
+ GridTable gt = mTab.getTableModel();
+ if (gt.isImporting()) {
+ trxName = gt.get_TrxName();
+ }
+ }
+ MInventory inventory = new MInventory(ctx, (Integer) mTab.getValue("M_Inventory_ID"), trxName);
+ if (MDocType.DOCSUBTYPEINV_CostAdjustment.equals(inventory.getC_DocType().getDocSubTypeInv())) {
+ String costingMethod = inventory.getCostingMethod();
+ Object productValue = mTab.getValue(I_M_InventoryLine.COLUMNNAME_M_Product_ID);
+ if (productValue == null || ((Integer)productValue).intValue() == 0) return null;
+
+ MProduct product = MProduct.get(ctx, (Integer)productValue);
+ int M_ASI_ID = value != null ? (Integer)value : 0;
+ int AD_Org_ID = inventory.getAD_Org_ID();
+ int C_Currency_ID = inventory.getC_Currency_ID();
+
+ MClient client = MClient.get(ctx);
+ MAcctSchema as = client.getAcctSchema();
+
+ if (as.getC_Currency_ID() != C_Currency_ID)
+ {
+ MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(ctx, client.get_ID());
+ for (int i = 0; i < ass.length ; i ++)
+ {
+ MAcctSchema a = ass[i];
+ if (a.getC_Currency_ID() == C_Currency_ID)
+ as = a ;
+ }
+ }
+ MCost cost = product.getCostingRecord(as, AD_Org_ID, M_ASI_ID, costingMethod);
+ if (cost == null) {
+ if (!MCostElement.COSTINGMETHOD_StandardCosting.equals(costingMethod)) {
+ mTab.setValue(mField, null);
+ return Msg.getMsg(Env.getCtx(), "NoCostingRecord");
+ }
+ }
+
+ if (cost != null) {
+ BigDecimal currentCost = (BigDecimal) mTab.getValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice);
+ if (currentCost == null || currentCost.compareTo(cost.getCurrentCostPrice())==0) return null;
+
+ mTab.setValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice, cost.getCurrentCostPrice());
+ mTab.setValue(I_M_InventoryLine.COLUMNNAME_NewCostPrice, cost.getCurrentCostPrice());
+ }
+
+ }
+ return null;
+ }
+
+}
diff --git a/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentLineProduct.java b/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentLineProduct.java
new file mode 100644
index 0000000000..7be1b4111d
--- /dev/null
+++ b/org.adempiere.base.callout/src/org/adempiere/base/callout/CostAdjustmentLineProduct.java
@@ -0,0 +1,110 @@
+/***********************************************************************
+ * 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.adempiere.base.callout;
+
+import java.math.BigDecimal;
+import java.util.Properties;
+
+import org.adempiere.base.IColumnCallout;
+import org.adempiere.base.annotation.Callout;
+import org.compiere.model.GridField;
+import org.compiere.model.GridTab;
+import org.compiere.model.GridTable;
+import org.compiere.model.I_M_InventoryLine;
+import org.compiere.model.MAcctSchema;
+import org.compiere.model.MClient;
+import org.compiere.model.MCost;
+import org.compiere.model.MCostElement;
+import org.compiere.model.MDocType;
+import org.compiere.model.MInventory;
+import org.compiere.model.MProduct;
+import org.compiere.util.Env;
+import org.compiere.util.Msg;
+
+/**
+ *
+ * @author hengsin
+ *
+ */
+@Callout(tableName = I_M_InventoryLine.Table_Name, columnName = I_M_InventoryLine.COLUMNNAME_M_Product_ID)
+public class CostAdjustmentLineProduct implements IColumnCallout {
+ @Override
+ public String start(Properties ctx, int WindowNo, GridTab mTab,
+ GridField mField, Object value, Object oldValue) {
+ String trxName = null;
+ if ( mTab != null
+ && mTab.getTableModel() != null) {
+ GridTable gt = mTab.getTableModel();
+ if (gt.isImporting()) {
+ trxName = gt.get_TrxName();
+ }
+ }
+ MInventory inventory = new MInventory(ctx, (Integer) mTab.getValue("M_Inventory_ID"), trxName);
+ if (MDocType.DOCSUBTYPEINV_CostAdjustment.equals(inventory.getC_DocType().getDocSubTypeInv())) {
+ String costingMethod = inventory.getCostingMethod();
+ if (value == null) {
+ mTab.setValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice, BigDecimal.ZERO);
+ mTab.setValue(I_M_InventoryLine.COLUMNNAME_NewCostPrice, BigDecimal.ZERO);
+ } else {
+ MProduct product = MProduct.get(ctx, (Integer) value);
+ MClient client = MClient.get(ctx);
+ MAcctSchema as = client.getAcctSchema();
+
+ String costingLevel = product.getCostingLevel(as);
+ if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(costingLevel)) {
+ mTab.setValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice, BigDecimal.ZERO);
+ mTab.setValue(I_M_InventoryLine.COLUMNNAME_NewCostPrice, BigDecimal.ZERO);
+ }else {
+ Object asiValue = mTab.getValue(I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+ int M_ASI_ID = asiValue != null ? (Integer)asiValue : 0;
+ int AD_Org_ID = inventory.getAD_Org_ID();
+ int C_Currency_ID = inventory.getC_Currency_ID();
+
+ if (as.getC_Currency_ID() != C_Currency_ID)
+ {
+ MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(ctx, client.get_ID());
+ for (int i = 0; i < ass.length ; i ++)
+ {
+ MAcctSchema a = ass[i];
+ if (a.getC_Currency_ID() == C_Currency_ID)
+ as = a ;
+ }
+ }
+ MCost cost = product.getCostingRecord(as, AD_Org_ID, M_ASI_ID, costingMethod);
+ if (cost == null) {
+ if (!MCostElement.COSTINGMETHOD_StandardCosting.equals(costingMethod)) {
+ mTab.setValue(mField, null);
+ return Msg.getMsg(Env.getCtx(), "NoCostingRecord");
+ }
+ }
+
+ mTab.setValue(I_M_InventoryLine.COLUMNNAME_CurrentCostPrice, cost.getCurrentCostPrice());
+ mTab.setValue(I_M_InventoryLine.COLUMNNAME_NewCostPrice, cost.getCurrentCostPrice());
+ }
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/org.adempiere.base.callout/src/org/adempiere/base/callout/factory/AnnotationBasedColumnCalloutFactoryImpl.java b/org.adempiere.base.callout/src/org/adempiere/base/callout/factory/AnnotationBasedColumnCalloutFactoryImpl.java
new file mode 100644
index 0000000000..aecf6112ae
--- /dev/null
+++ b/org.adempiere.base.callout/src/org/adempiere/base/callout/factory/AnnotationBasedColumnCalloutFactoryImpl.java
@@ -0,0 +1,47 @@
+/***********************************************************************
+ * 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.adempiere.base.callout.factory;
+
+import org.adempiere.base.AnnotationBasedColumnCalloutFactory;
+import org.adempiere.base.IColumnCalloutFactory;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ *
+ * @author hengsin
+ *
+ */
+@Component(immediate = true, service = IColumnCalloutFactory.class)
+public class AnnotationBasedColumnCalloutFactoryImpl extends AnnotationBasedColumnCalloutFactory {
+
+ public AnnotationBasedColumnCalloutFactoryImpl() {
+ }
+
+ @Override
+ protected String[] getPackages() {
+ return new String[] {"org.adempiere.base.callout","org.adempiere.model"};
+ }
+
+}
diff --git a/org.adempiere.base.callout/src/org/adempiere/model/CalloutBPartnerQuickEntry.java b/org.adempiere.base.callout/src/org/adempiere/model/CalloutBPartnerQuickEntry.java
index 7e990cb629..61ba170096 100644
--- a/org.adempiere.base.callout/src/org/adempiere/model/CalloutBPartnerQuickEntry.java
+++ b/org.adempiere.base.callout/src/org/adempiere/model/CalloutBPartnerQuickEntry.java
@@ -16,12 +16,14 @@ package org.adempiere.model;
import java.util.Properties;
import org.adempiere.base.IColumnCallout;
+import org.adempiere.base.annotation.Callout;
import org.compiere.model.GridField;
import org.compiere.model.GridTab;
import org.compiere.model.MBPartner;
import org.compiere.util.Env;
import org.compiere.util.Util;
+@Callout(tableName = MBPartner.Table_Name, columnName = "*")
public class CalloutBPartnerQuickEntry implements IColumnCallout {
@Override
diff --git a/org.adempiere.base.callout/OSGI-INF/bpartnerquickentrycalloutFactory.xml b/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedColumnCalloutFactory.xml
similarity index 50%
rename from org.adempiere.base.callout/OSGI-INF/bpartnerquickentrycalloutFactory.xml
rename to org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedColumnCalloutFactory.xml
index 91c9d02b1b..55c93c2086 100644
--- a/org.adempiere.base.callout/OSGI-INF/bpartnerquickentrycalloutFactory.xml
+++ b/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedColumnCalloutFactory.xml
@@ -1,7 +1,7 @@
-
-
+
-
+
+
\ No newline at end of file
diff --git a/org.adempiere.base/plugin.xml b/org.adempiere.base/plugin.xml
index 91943b4fc5..51cab1bbee 100644
--- a/org.adempiere.base/plugin.xml
+++ b/org.adempiere.base/plugin.xml
@@ -64,36 +64,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
>> tableNameMap = new HashMap<>();
+
+ private final Map[]> constructorCache = new ConcurrentHashMap<>();
+
+
+ public AnnotationBasedColumnCalloutFactory() {
+ }
+
+ @Override
+ public IColumnCallout[] getColumnCallouts(String tableName, String columnName) {
+ List callouts = new ArrayList();
+ ClassLoader classLoader = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
+ Map> columnNameMap = tableNameMap.get(tableName);
+ if (columnNameMap != null) {
+ List calloutClassNames = columnNameMap.get(columnName);
+ if (calloutClassNames != null) {
+ newCalloutInstance(callouts, classLoader, calloutClassNames);
+ }
+ calloutClassNames = columnNameMap.get("*");
+ if (calloutClassNames != null) {
+ newCalloutInstance(callouts, classLoader, calloutClassNames);
+ }
+ }
+
+ columnNameMap = tableNameMap.get("*");
+ if (columnNameMap != null) {
+ List calloutClassNames = columnNameMap.get(columnName);
+ if (calloutClassNames != null) {
+ newCalloutInstance(callouts, classLoader, calloutClassNames);
+ }
+ calloutClassNames = columnNameMap.get("*");
+ if (calloutClassNames != null) {
+ newCalloutInstance(callouts, classLoader, calloutClassNames);
+ }
+ }
+
+
+ return callouts.toArray(new IColumnCallout[0]);
+ }
+
+ private void newCalloutInstance(List callouts, ClassLoader classLoader,
+ List calloutClassNames) {
+ for(String calloutClass : calloutClassNames) {
+ Constructor>[] constructors = constructorCache.get(calloutClass);
+ if (constructors == null) {
+ try {
+ Class> clazz = classLoader.loadClass(calloutClass);
+ Constructor> constructor = clazz.getDeclaredConstructor();
+ IColumnCallout columnCallout = (IColumnCallout) constructor.newInstance();
+ callouts.add(columnCallout);
+ constructors = new Constructor>[] {constructor};
+ constructorCache.put(calloutClass, constructors);
+ } catch (Exception e) {
+ s_log.log(Level.WARNING, e.getMessage(), e);
+ constructors = new Constructor>[0];
+ constructorCache.put(calloutClass, constructors);
+ }
+ } else if (constructors.length == 1){
+ try {
+ IColumnCallout columnCallout = (IColumnCallout) constructors[0].newInstance();
+ callouts.add(columnCallout);
+ } catch (Exception e) {
+ s_log.log(Level.WARNING, e.getMessage(), e);
+ constructors = new Constructor>[0];
+ constructorCache.put(calloutClass, constructors);
+ }
+ }
+ }
+ }
+
+ /**
+ * Subclasses must override this method in order to provide packages to
+ * scan, discover and register {@link IColumnCallout} classes
+ * @return array of packages to be accepted during class scanning
+ * @see ClassGraph#acceptPackagesNonRecursive(String...)
+ */
+ protected abstract String[] getPackages();
+
+ @Activate
+ public void activate(ComponentContext context) throws ClassNotFoundException {
+ long start = System.currentTimeMillis();
+ bundleContext = context.getBundleContext();
+ ClassLoader classLoader = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
+
+ ClassGraph graph = new ClassGraph()
+ .enableAnnotationInfo()
+ .overrideClassLoaders(classLoader)
+ .disableNestedJarScanning()
+ .disableModuleScanning()
+ .acceptPackagesNonRecursive(getPackages());
+
+ try (ScanResult scanResult = graph.scan()) {
+ List processed = new ArrayList();
+ for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(Callouts.class)) {
+ if (classInfo.isAbstract())
+ continue;
+ String className = classInfo.getName();
+ AnnotationInfoList annotationInfos = classInfo.getAnnotationInfoRepeatable(Callout.class);
+ for(AnnotationInfo annotationInfo : annotationInfos) {
+ processAnnotation(className, annotationInfo);
+ }
+ processed.add(className);
+ }
+ for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(Callout.class)) {
+ if (classInfo.isAbstract())
+ continue;
+ String className = classInfo.getName();
+ if (processed.contains(className))
+ continue;
+ AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(Callout.class);
+ processAnnotation(className, annotationInfo);
+ }
+ }
+ long end = System.currentTimeMillis();
+ if (s_log.isLoggable(Level.INFO))
+ s_log.info(this.getClass().getSimpleName() + " loaded "+tableNameMap.size() +" classes in "
+ +((end-start)/1000f) + "s");
+ }
+
+ private void processAnnotation(String className, AnnotationInfo annotationInfo) {
+ //not sure why but sometime ClassGraph return Object[] instead of the expected String[]
+ Object[] tableNames = (Object[]) annotationInfo.getParameterValues().getValue("tableName");
+ Object[] columnNames = (Object[]) annotationInfo.getParameterValues().getValue("columnName");
+
+ boolean matchAllTables = false;
+ for(Object tableName : tableNames) {
+ if ("*".equals(tableName) ) {
+ matchAllTables = true;
+ break;
+ }
+ }
+
+ boolean matchAllColumns = false;
+ for(Object columnName : columnNames) {
+ if ("*".equals(columnName)) {
+ matchAllColumns = true;
+ break;
+ }
+ }
+
+ //not allow to match everything
+ if (matchAllColumns && matchAllTables)
+ return;
+
+ Map> columnNameMap = null;
+ if (matchAllTables) {
+ columnNameMap = tableNameMap.get("*");
+ if (columnNameMap == null) {
+ columnNameMap = new HashMap>();
+ tableNameMap.put("*", columnNameMap);
+ }
+ if (matchAllColumns) {
+ addCallout(className, columnNameMap);
+ } else {
+ addCallout(className, columnNames, columnNameMap);
+ }
+ } else {
+ for(Object tableName : tableNames) {
+ columnNameMap = tableNameMap.get(tableName);
+ if (columnNameMap == null) {
+ columnNameMap = new HashMap>();
+ tableNameMap.put((String)tableName, columnNameMap);
+ }
+ if (matchAllColumns) {
+ addCallout(className, columnNameMap);
+ } else {
+ addCallout(className, columnNames, columnNameMap);
+ }
+ }
+ }
+ }
+
+ private void addCallout(String className, Object[] columnNames, Map> columnNameMap) {
+ for (Object columnName : columnNames) {
+ List callouts = columnNameMap.get(columnName);
+ if (callouts == null ) {
+ callouts = new ArrayList();
+ columnNameMap.put((String)columnName, callouts);
+ }
+ callouts.add(className);
+ }
+ }
+
+ private void addCallout(String className, Map> columnNameMap) {
+ List callouts = columnNameMap.get("*");
+ if (callouts == null ) {
+ callouts = new ArrayList();
+ columnNameMap.put("*", callouts);
+ }
+ callouts.add(className);
+ }
+}
diff --git a/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedColumnCalloutFactory.java b/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedColumnCalloutFactory.java
new file mode 100644
index 0000000000..dd85d0a133
--- /dev/null
+++ b/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedColumnCalloutFactory.java
@@ -0,0 +1,45 @@
+/***********************************************************************
+ * 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.adempiere.base;
+
+import org.osgi.service.component.annotations.Component;
+
+/**
+ *
+ * @author hengsin
+ *
+ */
+@Component(immediate = true, service = IColumnCalloutFactory.class)
+public class DefaultAnnotationBasedColumnCalloutFactory extends AnnotationBasedColumnCalloutFactory {
+
+ public DefaultAnnotationBasedColumnCalloutFactory() {
+ }
+
+ @Override
+ protected String[] getPackages() {
+ return new String[] {"org.adempiere.model"};
+ }
+
+}
diff --git a/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java b/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java
index eb88ad01ba..0d73e29902 100644
--- a/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java
+++ b/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java
@@ -26,6 +26,8 @@ package org.adempiere.base;
import java.util.function.Supplier;
+import org.osgi.framework.BundleContext;
+
/**
*
* @author hengsin
@@ -49,4 +51,10 @@ public interface IMappedColumnCalloutFactory {
*/
public void removeMapping(String tableName, String columnName, Supplier supplier);
+ /**
+ * scan, discover and register classes with Callout annotation
+ * @param context
+ * @param packages
+ */
+ public void scan(BundleContext context, String... packages);
}
\ No newline at end of file
diff --git a/org.adempiere.base/src/org/adempiere/base/MappedColumnCalloutFactory.java b/org.adempiere.base/src/org/adempiere/base/MappedColumnCalloutFactory.java
index 5d52a60000..b39479a962 100644
--- a/org.adempiere.base/src/org/adempiere/base/MappedColumnCalloutFactory.java
+++ b/org.adempiere.base/src/org/adempiere/base/MappedColumnCalloutFactory.java
@@ -24,13 +24,26 @@
**********************************************************************/
package org.adempiere.base;
+import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.Supplier;
+import java.util.logging.Level;
+import org.adempiere.base.annotation.Callout;
+import org.adempiere.base.annotation.Callouts;
+import org.compiere.util.CLogger;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.BundleWiring;
import org.osgi.service.component.annotations.Component;
+import io.github.classgraph.AnnotationInfo;
+import io.github.classgraph.AnnotationInfoList;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
+
/**
*
* @author hengsin
@@ -42,6 +55,8 @@ import org.osgi.service.component.annotations.Component;
property = {"service.ranking:Integer=1"})
public class MappedColumnCalloutFactory implements IColumnCalloutFactory, IMappedColumnCalloutFactory {
+ private final static CLogger s_log = CLogger.getCLogger(MappedColumnCalloutFactory.class);
+
private final HashMap>> calloutMap = new HashMap<>();
/**
@@ -101,4 +116,114 @@ public class MappedColumnCalloutFactory implements IColumnCalloutFactory, IMappe
}
}
}
+
+ @Override
+ public void scan(BundleContext context, String... packages) {
+ ClassLoader classLoader = context.getBundle().adapt(BundleWiring.class).getClassLoader();
+
+ ClassGraph graph = new ClassGraph()
+ .enableAnnotationInfo()
+ .overrideClassLoaders(classLoader)
+ .disableNestedJarScanning()
+ .disableModuleScanning()
+ .acceptPackagesNonRecursive(packages);
+
+ try (ScanResult scanResult = graph.scan()) {
+ List processed = new ArrayList();
+ for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(Callouts.class)) {
+ if (classInfo.isAbstract())
+ continue;
+ String className = classInfo.getName();
+ AnnotationInfoList annotationInfos = classInfo.getAnnotationInfoRepeatable(Callout.class);
+ for(AnnotationInfo annotationInfo : annotationInfos) {
+ processAnnotation(className, annotationInfo, classLoader);
+ }
+ processed.add(className);
+ }
+ for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(Callout.class)) {
+ if (classInfo.isAbstract())
+ continue;
+ String className = classInfo.getName();
+ if (processed.contains(className))
+ continue;
+ AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(Callout.class);
+ processAnnotation(className, annotationInfo, classLoader);
+ }
+ }
+ }
+
+ private void processAnnotation(String className, AnnotationInfo annotationInfo, ClassLoader classLoader) {
+ Object[] tableNames = (Object[]) annotationInfo.getParameterValues().getValue("tableName");
+ Object[] columnNames = (Object[]) annotationInfo.getParameterValues().getValue("columnName");
+
+ boolean matchAllTables = false;
+ for(Object tableName : tableNames) {
+ if ("*".equals(tableName) ) {
+ matchAllTables = true;
+ break;
+ }
+ }
+
+ boolean matchAllColumns = false;
+ for(Object columnName : columnNames) {
+ if ("*".equals(columnName)) {
+ matchAllColumns = true;
+ break;
+ }
+ }
+
+ //not allow to match everything
+ if (matchAllColumns && matchAllTables)
+ return;
+
+ try {
+ Class> clazz = classLoader.loadClass(className);
+ Constructor> constructor = clazz.getDeclaredConstructor();
+ CalloutSupplier supplier = new CalloutSupplier(constructor);
+ if (matchAllTables) {
+ for(Object columnName : columnNames) {
+ addMapping("*", (String)columnName, supplier);
+ }
+ } else {
+ for(Object tableName : tableNames) {
+ if (matchAllColumns) {
+ addMapping((String)tableName, "*", supplier);
+ } else {
+ for(Object columnName : columnNames) {
+ addMapping((String)tableName, (String)columnName, supplier);
+ }
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ if (s_log.isLoggable(Level.INFO))
+ s_log.log(Level.INFO, e.getMessage(), e);
+ }
+
+ }
+
+ private static class CalloutSupplier implements Supplier {
+
+ private Constructor> constructor;
+
+ private CalloutSupplier(Constructor> constructor) {
+ this.constructor = constructor;
+ }
+
+ @Override
+ public IColumnCallout get() {
+ IColumnCallout callout = null;
+ if (constructor != null) {
+ try {
+ callout = (IColumnCallout) constructor.newInstance();
+ } catch (Exception e) {
+ constructor = null;
+ s_log.log(Level.WARNING, e.getMessage(), e);
+ }
+ }
+ return callout;
+ }
+
+ }
}
diff --git a/org.adempiere.base/src/org/adempiere/base/annotation/Callout.java b/org.adempiere.base/src/org/adempiere/base/annotation/Callout.java
new file mode 100644
index 0000000000..a3120dad41
--- /dev/null
+++ b/org.adempiere.base/src/org/adempiere/base/annotation/Callout.java
@@ -0,0 +1,56 @@
+/***********************************************************************
+ * 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.adempiere.base.annotation;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for Column Callout. This should only be used with class that implements the IColumnCallout interface.
+ * You can repeat the annotation multiple time for different table and column name combination
+ * Note that you can't use * for both tableName and columnName attribute.
+ * @author hengsin
+ *
+ */
+@Retention(RUNTIME)
+@Target(ElementType.TYPE)
+@Repeatable(Callouts.class)
+public @interface Callout {
+ /**
+ *
+ * @return table names or * to match all tables
+ */
+ String[] tableName();
+
+ /**
+ *
+ * @return column names or * to match all columns
+ */
+ String[] columnName();
+}
diff --git a/org.adempiere.base/src/org/adempiere/base/annotation/Callouts.java b/org.adempiere.base/src/org/adempiere/base/annotation/Callouts.java
new file mode 100644
index 0000000000..a1a71d002a
--- /dev/null
+++ b/org.adempiere.base/src/org/adempiere/base/annotation/Callouts.java
@@ -0,0 +1,42 @@
+/***********************************************************************
+ * 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.adempiere.base.annotation;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author hengsin
+ *
+ */
+@Retention(RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Callouts {
+ Callout[] value() default {};
+}
diff --git a/org.adempiere.base/src/org/adempiere/model/CalloutInfoWindow.java b/org.adempiere.base/src/org/adempiere/model/CalloutInfoWindow.java
index dfafcb60e4..b8eedc1069 100644
--- a/org.adempiere.base/src/org/adempiere/model/CalloutInfoWindow.java
+++ b/org.adempiere.base/src/org/adempiere/model/CalloutInfoWindow.java
@@ -18,6 +18,7 @@ import java.util.Map;
import java.util.Properties;
import org.adempiere.base.IColumnCallout;
+import org.adempiere.base.annotation.Callout;
import org.compiere.model.AccessSqlParser;
import org.compiere.model.AccessSqlParser.TableInfo;
import org.compiere.model.GridField;
@@ -35,6 +36,8 @@ import org.compiere.util.Env;
* @author hengsin
*
*/
+@Callout(tableName = "AD_InfoWindow", columnName = "AD_Table_ID")
+@Callout(tableName = "AD_InfoColumn", columnName = {"AD_Element_ID","AD_Reference_ID"})
public class CalloutInfoWindow implements IColumnCallout {
/**
diff --git a/org.idempiere.test/src/org/idempiere/test/model/CalloutTest.java b/org.idempiere.test/src/org/idempiere/test/model/CalloutTest.java
new file mode 100644
index 0000000000..76a033c002
--- /dev/null
+++ b/org.idempiere.test/src/org/idempiere/test/model/CalloutTest.java
@@ -0,0 +1,188 @@
+/***********************************************************************
+ * 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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.adempiere.base.ColumnCalloutManager;
+import org.adempiere.base.Core;
+import org.adempiere.base.IColumnCallout;
+import org.adempiere.model.CalloutInfoWindow;
+import org.compiere.Adempiere;
+import org.compiere.model.Callout;
+import org.compiere.model.I_M_InventoryLine;
+import org.compiere.model.MBPartner;
+import org.compiere.model.MColumn;
+import org.compiere.model.MRule;
+import org.compiere.model.Query;
+import org.compiere.util.Env;
+import org.idempiere.test.AbstractTestCase;
+import org.junit.jupiter.api.Test;
+
+/**
+ *
+ * @author hengsin
+ *
+ */
+public class CalloutTest extends AbstractTestCase {
+
+ public CalloutTest() {
+ }
+
+ @Test
+ public void testAnnotatedCallout() {
+ List callouts = ColumnCalloutManager.findCallout("AD_InfoWindow", "AD_Table_ID");
+ assertNotNull(callouts, "Null column callouts for AD_InfoWindow.AD_Table_ID");
+ assertTrue(callouts.size() > 0, "Empty column callouts for AD_InfoWindow.AD_Table_ID");
+ int found = 0;
+ for(IColumnCallout callout : callouts) {
+ if (callout instanceof CalloutInfoWindow) {
+ found++;
+ }
+ }
+ assertTrue(found==1, CalloutInfoWindow.class.getName() + " not found for AD_InfoWindow.AD_Table_ID");
+
+ callouts = ColumnCalloutManager.findCallout("AD_InfoColumn", "AD_Element_ID");
+ assertNotNull(callouts, "Null column callouts for AD_InfoColumn.AD_Element_ID");
+ assertTrue(callouts.size() > 0, "Empty column callouts for AD_InfoColumn.AD_Element_ID");
+ found = 0;
+ for(IColumnCallout callout : callouts) {
+ if (callout instanceof CalloutInfoWindow) {
+ found++;
+ }
+ }
+ assertTrue(found==1, CalloutInfoWindow.class.getName() + " not found for AD_InfoColumn.AD_Element_ID");
+
+ callouts = ColumnCalloutManager.findCallout("AD_InfoColumn", "AD_Reference_ID");
+ assertNotNull(callouts, "Null column callouts for AD_InfoColumn.AD_Reference_ID");
+ assertTrue(callouts.size() > 0, "Empty column callouts for AD_InfoColumn.AD_Reference_ID");
+ found = 0;
+ for(IColumnCallout callout : callouts) {
+ if (callout instanceof CalloutInfoWindow) {
+ found++;
+ }
+ }
+ assertTrue(found==1, CalloutInfoWindow.class.getName() + " not found for AD_InfoColumn.AD_Reference_ID");
+
+ String calloutClass = "org.adempiere.base.callout.CostAdjustmentLineASI";
+ callouts = ColumnCalloutManager.findCallout(I_M_InventoryLine.Table_Name, I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+ assertNotNull(callouts, "Null column callouts for "+I_M_InventoryLine.Table_Name+"."+I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+ assertTrue(callouts.size() > 0, "Empty column callouts for "+I_M_InventoryLine.Table_Name+"."+I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+ found = 0;
+ for(IColumnCallout callout : callouts) {
+ if (callout.getClass().getName().equals(calloutClass)) {
+ found++;
+ }
+ }
+ assertTrue(found==1, calloutClass+" not found for "+I_M_InventoryLine.Table_Name+"."
+ +I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+
+ calloutClass = "org.adempiere.base.callout.CostAdjustmentLineProduct";
+ callouts = ColumnCalloutManager.findCallout(I_M_InventoryLine.Table_Name, I_M_InventoryLine.COLUMNNAME_M_Product_ID);
+ assertNotNull(callouts, "Null column callouts for "+I_M_InventoryLine.Table_Name+"."+I_M_InventoryLine.COLUMNNAME_M_Product_ID);
+ assertTrue(callouts.size() > 0, "Empty column callouts for "+I_M_InventoryLine.Table_Name+"."+I_M_InventoryLine.COLUMNNAME_M_Product_ID);
+ found = 0;
+ for(IColumnCallout callout : callouts) {
+ if (callout.getClass().getName().equals(calloutClass)) {
+ found++;
+ }
+ }
+ assertTrue(found==1, calloutClass+" not found for "+I_M_InventoryLine.Table_Name+"."
+ +I_M_InventoryLine.COLUMNNAME_M_Product_ID);
+
+ calloutClass = "org.adempiere.base.callout.CostAdjustmentLineProduct";
+ callouts = ColumnCalloutManager.findCallout(I_M_InventoryLine.Table_Name, I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+ assertNotNull(callouts, "Null column callouts for "+I_M_InventoryLine.Table_Name+"."+I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+ assertTrue(callouts.size() > 0, "Empty column callouts for "+I_M_InventoryLine.Table_Name+"."+I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+ found = 0;
+ for(IColumnCallout callout : callouts) {
+ if (callout.getClass().getName().equals(calloutClass)) {
+ found++;
+ }
+ }
+ assertTrue(found==0, calloutClass+" found for "+I_M_InventoryLine.Table_Name+"."
+ +I_M_InventoryLine.COLUMNNAME_M_AttributeSetInstance_ID);
+
+ calloutClass = "org.adempiere.model.CalloutBPartnerQuickEntry";
+ callouts = ColumnCalloutManager.findCallout(MBPartner.Table_Name, MBPartner.COLUMNNAME_Name);
+ assertNotNull(callouts, "Null column callouts for "+MBPartner.Table_Name+"."+MBPartner.COLUMNNAME_Name);
+ assertTrue(callouts.size() > 0, "Empty column callouts for "+MBPartner.Table_Name+"."+MBPartner.COLUMNNAME_Name);
+ found = 0;
+ for(IColumnCallout callout : callouts) {
+ if (callout.getClass().getName().equals(calloutClass)) {
+ found++;
+ }
+ }
+ assertTrue(found==1, calloutClass+" not found for "+MBPartner.Table_Name+"."
+ +MBPartner.COLUMNNAME_Name);
+ }
+
+ @Test
+ public void testCoreCalloutMapping() {
+ Query query = new Query(Env.getCtx(), MColumn.Table_Name, "AD_Column.IsActive='Y' AND AD_Column.AD_Column_ID < 1000000 "
+ + "AND AD_Table.IsActive='Y' AND AD_Table.AD_Table_ID < 1000000 "
+ + "AND AD_Column.Callout IS NOT NULL ", getTrxName());
+ query.addJoinClause("JOIN AD_Table ON (AD_Column.AD_Table_ID=AD_Table.AD_Table_ID)");
+ query.addJoinClause("JOIN AD_Tab ON (AD_Table.AD_Table_ID=AD_Tab.AD_Table_ID AND AD_Tab.IsActive='Y' AND AD_Tab.AD_Tab_ID<1000000)");
+ query.addJoinClause("JOIN AD_Window ON (AD_Tab.AD_Window_ID=AD_Window.AD_Window_ID AND AD_Window.IsActive='Y' AND AD_Window.AD_Window_ID<1000000)");
+ query.addJoinClause("JOIN AD_Menu ON (AD_Window.AD_Window_ID=AD_Menu.AD_Window_ID AND AD_Menu.IsActive='Y' AND AD_Menu.AD_Menu_ID<1000000)");
+ List columns = query.list();
+ ClassLoader baseClassLoader = Adempiere.class.getClassLoader();
+ int calloutCount = 0;
+ for(MColumn column : columns) {
+ StringTokenizer st = new StringTokenizer(column.getCallout(), ";,", false);
+ while (st.hasMoreTokens()) {
+ String cmd = st.nextToken().trim();
+
+ if (cmd.toLowerCase().startsWith(MRule.SCRIPT_PREFIX))
+ continue;
+ int methodStart = cmd.lastIndexOf('.');
+ try {
+ if (methodStart != -1) // no class
+ {
+ String className = cmd.substring(0,methodStart);
+ String method = cmd.substring(methodStart+1);
+ Callout callout = Core.getCallout(className, method);
+ if (callout == null) {
+ //no match from factory, check java classpath
+ Class> cClass = baseClassLoader.loadClass(className);
+ callout = (Callout)cClass.getDeclaredConstructor().newInstance();
+ }
+ assertNotNull(callout, "Can't get callout "+cmd+" for "+column.getAD_Table()+" "+column);
+ calloutCount++;
+ }
+ } catch (Exception e) {
+ fail("Can't get callout "+cmd+" for "+column.getAD_Table()+" "+column, e);
+ }
+ }
+ }
+ assertTrue(calloutCount > 0, "Zero callout loaded");
+ }
+}
diff --git a/org.idempiere.test/src/org/idempiere/test/model/MappedColumnCalloutFactoryTest.java b/org.idempiere.test/src/org/idempiere/test/model/MappedColumnCalloutFactoryTest.java
index 033ad87402..0ccf1be8d5 100644
--- a/org.idempiere.test/src/org/idempiere/test/model/MappedColumnCalloutFactoryTest.java
+++ b/org.idempiere.test/src/org/idempiere/test/model/MappedColumnCalloutFactoryTest.java
@@ -39,6 +39,7 @@ import org.compiere.model.GridTab;
import org.compiere.model.MTest;
import org.idempiere.test.AbstractTestCase;
import org.idempiere.test.TestActivator;
+import org.idempiere.test.model.annotated.MyAnnotatedTestQtyCallout;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@@ -67,6 +68,11 @@ public class MappedColumnCalloutFactoryTest extends AbstractTestCase {
var list = Core.findCallout(MTest.Table_Name, MTest.COLUMNNAME_T_Amount);
var optional = list.stream().filter(e -> e instanceof MyTestAmountCallout).findFirst();
assertTrue(optional.isPresent(), "Can't find MyTestAmountCallout column callout for " + MTest.Table_Name + "." + MTest.COLUMNNAME_T_Amount);
+
+ factory.scan(TestActivator.context, "org.idempiere.test.model.annotated");
+ list = Core.findCallout(MTest.Table_Name, MTest.COLUMNNAME_T_Qty);
+ optional = list.stream().filter(e -> e instanceof MyAnnotatedTestQtyCallout).findFirst();
+ assertTrue(optional.isPresent(), "Can't find MyAnnotatedTestQtyCallout column callout for " + MTest.Table_Name + "." + MTest.COLUMNNAME_T_Qty);
}
@Test
diff --git a/org.idempiere.test/src/org/idempiere/test/model/annotated/MyAnnotatedTestQtyCallout.java b/org.idempiere.test/src/org/idempiere/test/model/annotated/MyAnnotatedTestQtyCallout.java
new file mode 100644
index 0000000000..1b7cec5827
--- /dev/null
+++ b/org.idempiere.test/src/org/idempiere/test/model/annotated/MyAnnotatedTestQtyCallout.java
@@ -0,0 +1,51 @@
+/***********************************************************************
+ * 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.annotated;
+
+import java.util.Properties;
+
+import org.adempiere.base.IColumnCallout;
+import org.adempiere.base.annotation.Callout;
+import org.compiere.model.GridField;
+import org.compiere.model.GridTab;
+import org.compiere.model.MTest;
+
+/**
+ *
+ * @author hengsin
+ *
+ */
+@Callout(tableName = MTest.Table_Name, columnName = MTest.COLUMNNAME_T_Qty)
+public class MyAnnotatedTestQtyCallout implements IColumnCallout {
+
+ public MyAnnotatedTestQtyCallout() {
+ }
+
+ @Override
+ public String start(Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value, Object oldValue) {
+ return null;
+ }
+
+}