diff --git a/org.adempiere.base/src/org/adempiere/base/ColumnCalloutManager.java b/org.adempiere.base/src/org/adempiere/base/ColumnCalloutManager.java new file mode 100644 index 0000000000..854074710e --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/ColumnCalloutManager.java @@ -0,0 +1,194 @@ +/*********************************************************************** + * 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 java.util.ArrayList; +import java.util.List; + +import org.compiere.model.Callout; +import org.compiere.util.CCache; +import org.osgi.framework.ServiceReference; + +/** + * Contains static methods for retrieval of {@link IColumnCallout}, {@link Callout} and default {@link IMappedColumnCalloutFactory} + * @author hengsin + * + */ +public final class ColumnCalloutManager { + + /** + * Virtual table name for {@link Callout} factory cache. + * Call CacheMgt.get().reset(_ICALLOUT_FACTORY_CACHE_TABLE_NAME) to reset Callout factory cache. + */ + public static final String ICALLOUT_FACTORY_CACHE_TABLE_NAME = "_ICalloutFactory_Cache"; + + /** + * Virtual table name for {@link IColumnCallout} factory cache. + * Call CacheMgt.get().reset(LIST_ICOLUMNCALLOUT_FACTORY_CACHE_TABLE_NAME) to reset IColumnCallout factory cache. + */ + public static final String LIST_ICOLUMNCALLOUT_FACTORY_CACHE_TABLE_NAME = "_List_IColumnCalloutFactory_Cache"; + + private static final CCache> s_calloutFactoryCache = new CCache<>(ICALLOUT_FACTORY_CACHE_TABLE_NAME, "ICalloutFactory", 100, false); + private static final CCache>> s_columnCalloutFactoryCache = new CCache<>(LIST_ICOLUMNCALLOUT_FACTORY_CACHE_TABLE_NAME, "List", 100, false); + private static final CCache>> s_columnCalloutFactoryNegativeCache = new CCache<>(LIST_ICOLUMNCALLOUT_FACTORY_CACHE_TABLE_NAME, "List Negative", 100, false); + private static IServiceReferenceHolder s_mappedColumnCalloutFactoryReference = null; + + private ColumnCalloutManager() { + } + + /** + * + * @param tableName + * @param columnName + * @return list of {@link IColumnCallout} register for tableName.columnName + */ + public static List findCallout(String tableName, String columnName) { + List list = new ArrayList(); + + String cacheKey = tableName + "." + columnName; + List> cache = s_columnCalloutFactoryCache.get(cacheKey); + List> negativeCache = s_columnCalloutFactoryNegativeCache.get(cacheKey); + List> negativeServiceReferences = new ArrayList>(); + if (negativeCache != null) { + negativeServiceReferences.addAll(negativeCache); + } + List> cacheReferences = new ArrayList>(); + List> positiveReferenceHolders = new ArrayList<>(); + if (cache != null) { + for (IServiceReferenceHolder referenceHolder : cache) { + cacheReferences.add(referenceHolder.getServiceReference()); + IColumnCalloutFactory service = referenceHolder.getService(); + if (service != null) { + IColumnCallout[] callouts = service.getColumnCallouts(tableName, columnName); + if (callouts != null && callouts.length > 0) { + for(IColumnCallout callout : callouts) { + list.add(callout); + } + positiveReferenceHolders.add(referenceHolder); + } else { + negativeServiceReferences.add(referenceHolder.getServiceReference()); + } + } + } + } + + //init s_mappedColumnCalloutFactoryReference + getMappedColumnCalloutFactory(); + + int positiveAdded = 0; + int negativeAdded = 0; + List> referenceHolders = Service.locator().list(IColumnCalloutFactory.class).getServiceReferences(); + if (referenceHolders != null) { + for(IServiceReferenceHolder referenceHolder : referenceHolders) { + ServiceReference serviceReference = referenceHolder.getServiceReference(); + if (cacheReferences.contains(serviceReference) || negativeServiceReferences.contains(serviceReference)) + continue; + IColumnCalloutFactory service = referenceHolder.getService(); + if (service != null) { + IColumnCallout[] callouts = service.getColumnCallouts(tableName, columnName); + if (callouts != null && callouts.length > 0) { + for(IColumnCallout callout : callouts) { + list.add(callout); + } + positiveReferenceHolders.add(referenceHolder); + positiveAdded++; + } else { + synchronized (ColumnCalloutManager.class) { + //don't add s_mappedColumnCalloutFactoryReference to negative cache since it is dynamic + if (s_mappedColumnCalloutFactoryReference != null && serviceReference.equals(s_mappedColumnCalloutFactoryReference.getServiceReference())) + continue; + } + negativeServiceReferences.add(serviceReference); + negativeAdded++; + } + } + } + } + + if (cache == null || cache.size() != positiveReferenceHolders.size() || positiveAdded > 0) + s_columnCalloutFactoryCache.put(cacheKey, positiveReferenceHolders); + if (negativeCache == null || negativeCache.size() != negativeServiceReferences.size() || negativeAdded > 0) + s_columnCalloutFactoryNegativeCache.put(cacheKey, negativeServiceReferences); + + return list; + } + + // IDEMPIERE-2732 + /** + * + * @param className + * @param methodName + * @return {@link Callout} for className and methodName + */ + public static Callout getCallout(String className, String methodName) { + String cacheKey = className + "::" + methodName; + IServiceReferenceHolder cache = s_calloutFactoryCache.get(cacheKey); + if (cache != null) { + ICalloutFactory service = cache.getService(); + if (service != null) { + Callout callout = service.getCallout(className, methodName); + if (callout != null) { + return callout; + } + } + s_calloutFactoryCache.remove(cacheKey); + } + List> factories = Service.locator().list(ICalloutFactory.class).getServiceReferences(); + if (factories != null) { + for(IServiceReferenceHolder factory : factories) { + ICalloutFactory service = factory.getService(); + if (service != null) { + Callout callout = service.getCallout(className, methodName); + if (callout != null) { + s_calloutFactoryCache.put(cacheKey, factory); + return callout; + } + } + } + } + return null; + } + + /** + * + * @return {@link IMappedColumnCalloutFactory} + */ + public synchronized static IMappedColumnCalloutFactory getMappedColumnCalloutFactory() { + IMappedColumnCalloutFactory factoryService = null; + if (s_mappedColumnCalloutFactoryReference != null) { + factoryService = s_mappedColumnCalloutFactoryReference.getService(); + if (factoryService != null) + return factoryService; + else + s_mappedColumnCalloutFactoryReference = null; + } + IServiceReferenceHolder serviceReference = Service.locator().locate(IMappedColumnCalloutFactory.class).getServiceReference(); + if (serviceReference != null) { + factoryService = serviceReference.getService(); + s_mappedColumnCalloutFactoryReference = serviceReference; + } + return factoryService; + } +} diff --git a/org.adempiere.base/src/org/adempiere/base/Core.java b/org.adempiere.base/src/org/adempiere/base/Core.java index 268f68d8b0..a6df2c6ffe 100644 --- a/org.adempiere.base/src/org/adempiere/base/Core.java +++ b/org.adempiere.base/src/org/adempiere/base/Core.java @@ -21,7 +21,6 @@ package org.adempiere.base; import java.net.URL; -import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -58,7 +57,6 @@ import org.idempiere.fa.service.api.IDepreciationMethod; import org.idempiere.fa.service.api.IDepreciationMethodFactory; import org.idempiere.model.IMappedModelFactory; import org.idempiere.process.IMappedProcessFactory; -import org.osgi.framework.ServiceReference; /** * This is a facade class for the Service Locator. @@ -106,9 +104,6 @@ public class Core { }; } - private static final CCache>> s_columnCalloutFactoryCache = new CCache<>(null, "List", 100, false); - private static final CCache>> s_columnCalloutFactoryNegativeCache = new CCache<>(null, "List Negative", 100, false); - /** * * @param tableName @@ -116,70 +111,9 @@ public class Core { * @return list of callout register for tableName.columnName */ public static List findCallout(String tableName, String columnName) { - List list = new ArrayList(); - - String cacheKey = tableName + "." + columnName; - List> cache = s_columnCalloutFactoryCache.get(cacheKey); - List> negativeCache = s_columnCalloutFactoryNegativeCache.get(cacheKey); - List> negativeServiceReferences = new ArrayList>(); - if (negativeCache != null) { - negativeServiceReferences.addAll(negativeCache); - } - List> cacheReferences = new ArrayList>(); - List> positiveReferenceHolders = new ArrayList<>(); - if (cache != null) { - for (IServiceReferenceHolder referenceHolder : cache) { - cacheReferences.add(referenceHolder.getServiceReference()); - IColumnCalloutFactory service = referenceHolder.getService(); - if (service != null) { - IColumnCallout[] callouts = service.getColumnCallouts(tableName, columnName); - if (callouts != null && callouts.length > 0) { - for(IColumnCallout callout : callouts) { - list.add(callout); - } - positiveReferenceHolders.add(referenceHolder); - } else { - negativeServiceReferences.add(referenceHolder.getServiceReference()); - } - } - } - } - - int positiveAdded = 0; - int negativeAdded = 0; - List> referenceHolders = Service.locator().list(IColumnCalloutFactory.class).getServiceReferences(); - if (referenceHolders != null) { - for(IServiceReferenceHolder referenceHolder : referenceHolders) { - if (cacheReferences.contains(referenceHolder.getServiceReference()) || negativeServiceReferences.contains(referenceHolder.getServiceReference())) - continue; - IColumnCalloutFactory service = referenceHolder.getService(); - if (service != null) { - IColumnCallout[] callouts = service.getColumnCallouts(tableName, columnName); - if (callouts != null && callouts.length > 0) { - for(IColumnCallout callout : callouts) { - list.add(callout); - } - positiveReferenceHolders.add(referenceHolder); - positiveAdded++; - } else { - negativeServiceReferences.add(referenceHolder.getServiceReference()); - negativeAdded++; - } - } - } - } - - if (cache == null || cache.size() != positiveReferenceHolders.size() || positiveAdded > 0) - s_columnCalloutFactoryCache.put(cacheKey, positiveReferenceHolders); - if (negativeCache == null || negativeCache.size() != negativeServiceReferences.size() || negativeAdded > 0) - s_columnCalloutFactoryNegativeCache.put(cacheKey, negativeServiceReferences); - - return list; + return ColumnCalloutManager.findCallout(tableName, columnName); } - private static final CCache> s_calloutFactoryCache = new CCache<>(null, "ICalloutFactory", 100, false); - - // IDEMPIERE-2732 /** * * @param className @@ -187,32 +121,7 @@ public class Core { * @return callout for className */ public static Callout getCallout(String className, String methodName) { - String cacheKey = className + "::" + methodName; - IServiceReferenceHolder cache = s_calloutFactoryCache.get(cacheKey); - if (cache != null) { - ICalloutFactory service = cache.getService(); - if (service != null) { - Callout callout = service.getCallout(className, methodName); - if (callout != null) { - return callout; - } - } - s_calloutFactoryCache.remove(cacheKey); - } - List> factories = Service.locator().list(ICalloutFactory.class).getServiceReferences(); - if (factories != null) { - for(IServiceReferenceHolder factory : factories) { - ICalloutFactory service = factory.getService(); - if (service != null) { - Callout callout = service.getCallout(className, methodName); - if (callout != null) { - s_calloutFactoryCache.put(cacheKey, factory); - return callout; - } - } - } - } - return null; + return ColumnCalloutManager.getCallout(className, methodName); } private static final CCache> s_processFactoryCache = new CCache<>(null, "IProcessFactory", 100, false); @@ -983,25 +892,12 @@ public class Core { return processFactoryService; } - private static IServiceReferenceHolder s_mappedColumnCalloutFactoryReference = null; - /** * * @return {@link IMappedColumnCalloutFactory} */ public static IMappedColumnCalloutFactory getMappedColumnCalloutFactory() { - IMappedColumnCalloutFactory factoryService = null; - if (s_mappedColumnCalloutFactoryReference != null) { - factoryService = s_mappedColumnCalloutFactoryReference.getService(); - if (factoryService != null) - return factoryService; - } - IServiceReferenceHolder serviceReference = Service.locator().locate(IMappedColumnCalloutFactory.class).getServiceReference(); - if (serviceReference != null) { - factoryService = serviceReference.getService(); - s_mappedColumnCalloutFactoryReference = serviceReference; - } - return factoryService; + return ColumnCalloutManager.getMappedColumnCalloutFactory(); } private static IServiceReferenceHolder s_mappedDocumentFactoryReference = null; diff --git a/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java b/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java index a2246fe52c..eb88ad01ba 100644 --- a/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java +++ b/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java @@ -39,7 +39,7 @@ public interface IMappedColumnCalloutFactory { * @param columnName case insensitive column name or * to match all column * @param supplier supplier for {@link IColumnCallout} instance */ - void addMapping(String tableName, String columnName, Supplier supplier); + public void addMapping(String tableName, String columnName, Supplier supplier); /** * remove mapping for callout @@ -47,6 +47,6 @@ public interface IMappedColumnCalloutFactory { * @param columnName case insensitive column name or * to match all column * @param supplier supplier for {@link IColumnCallout} instance */ - void removeMapping(String tableName, String columnName, Supplier supplier); + public void removeMapping(String tableName, String columnName, Supplier supplier); } \ No newline at end of file diff --git a/org.idempiere.test/src/org/idempiere/test/adwindow/GridTabTest.java b/org.idempiere.test/src/org/idempiere/test/adwindow/GridTabTest.java new file mode 100644 index 0000000000..d32b2e5d82 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/adwindow/GridTabTest.java @@ -0,0 +1,180 @@ +/*********************************************************************** + * 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.adwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.compiere.model.GridField; +import org.compiere.model.GridTab; +import org.compiere.model.GridWindow; +import org.compiere.model.GridWindowVO; +import org.compiere.model.MBPartner; +import org.compiere.model.MOrder; +import org.compiere.model.MQuery; +import org.compiere.util.Env; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; + +/** + * + * @author hengsin + * + */ +public class GridTabTest extends AbstractTestCase { + + ////Business Partner Job Block + private final static int BP_JOE_BLOCK = 118; + //Business Partner Window + private final static int BP_WINDOW_ID = 123; + //Sales Order Window + private final static int SO_WINDOW_ID = 143; + + public GridTabTest() { + } + + @Test + public void testQuery() { + int AD_Window_ID = BP_WINDOW_ID; + var gWindowVO = GridWindowVO.create (Env.getCtx(), 1, AD_Window_ID); + var gridWindow = new GridWindow(gWindowVO, true); + int tabCount = gridWindow.getTabCount(); + assertTrue(tabCount > 0, "Tab Count is Zero. AD_Window_ID="+AD_Window_ID); + + MQuery query = new MQuery(MBPartner.Table_Name); + query.addRestriction(MBPartner.COLUMNNAME_C_BPartner_ID, MQuery.EQUAL, BP_JOE_BLOCK); + + for(int i = 0; i < gridWindow.getTabCount(); i++) { + gridWindow.initTab(i); + GridTab gTab = gridWindow.getTab(i); + if (i == 0) { + gTab.setUpdateWindowContext(true); + gTab.setQuery(query); + gTab.query(false, 0, 0); + } else { + gTab.setUpdateWindowContext(false); + } + } + + GridTab gTab = gridWindow.getTab(0); + assertTrue(gTab.getRowCount()==1, "GridTab Row Count is not 1. GridTab="+gTab.getName()); + + String name = (String) gTab.getValue("Name"); + MBPartner bpartner = new MBPartner(Env.getCtx(), BP_JOE_BLOCK, getTrxName()); + assertTrue(bpartner.getName().equals(name), "GridTab Name != MBPartner.getName(). GridTab.Name="+name + " MBPartner.getName="+bpartner.getName()); + } + + @Test + public void testCallout() { + //Sales Order + int AD_Window_ID = SO_WINDOW_ID; + var gWindowVO = GridWindowVO.create (Env.getCtx(), 1, AD_Window_ID); + var gridWindow = new GridWindow(gWindowVO, true); + int tabCount = gridWindow.getTabCount(); + assertTrue(tabCount > 0, "Tab Count is Zero. AD_Window_ID="+AD_Window_ID); + + for(int i = 0; i < gridWindow.getTabCount(); i++) { + gridWindow.initTab(i); + GridTab gTab = gridWindow.getTab(i); + if (i == 0) { + gTab.setUpdateWindowContext(true); + gTab.setQuery(null); + gTab.query(false, 7, 0); + } else { + gTab.setUpdateWindowContext(false); + } + } + + //insert new row + GridTab gTab = gridWindow.getTab(0); + gTab.dataNew(false); + assertTrue(gTab.isNew(), "Grid Tab dataNew call not working as expected"); + + //initial value of Bill_BPartner_ID should be null + assertNull(gTab.getValue(MOrder.COLUMNNAME_Bill_BPartner_ID), "Bill_BPartner_ID not null"); + gTab.setValue(MOrder.COLUMNNAME_C_BPartner_ID, BP_JOE_BLOCK); + + //set C_BPartner_ID to BP_JOE_BLOCK + Object value = gTab.getValue(MOrder.COLUMNNAME_C_BPartner_ID); + assertNotNull(value, "C_BPartner_ID is null"); + assertEquals(BP_JOE_BLOCK, ((Number)value).intValue(), "C_BPartner_ID not equals to " + BP_JOE_BLOCK); + + //invoke org.compiere.model.CalloutOrder.bPartner + GridField mField = gTab.getField(MOrder.COLUMNNAME_C_BPartner_ID); + gTab.processFieldChange(mField); + + //org.compiere.model.CalloutOrder.bPartner should set Bill_BPartner_ID to BP_JOE_BLOCK + value = gTab.getValue(MOrder.COLUMNNAME_Bill_BPartner_ID); + assertNotNull(value, "Bill_BPartner_ID is null"); + assertEquals(BP_JOE_BLOCK, ((Number)value).intValue(), "Bill_BPartner_ID not equals to " + BP_JOE_BLOCK); + } + + @Test + public void testUpdate() { + //Business Partner + int AD_Window_ID = BP_WINDOW_ID; + var gWindowVO = GridWindowVO.create (Env.getCtx(), 1, AD_Window_ID); + var gridWindow = new GridWindow(gWindowVO, true); + int tabCount = gridWindow.getTabCount(); + assertTrue(tabCount > 0, "Tab Count is Zero. AD_Window_ID="+AD_Window_ID); + + //retrieve for update + MQuery query = new MQuery(MBPartner.Table_Name); + query.addRestriction(MBPartner.COLUMNNAME_C_BPartner_ID, MQuery.EQUAL, BP_JOE_BLOCK); + + for(int i = 0; i < gridWindow.getTabCount(); i++) { + gridWindow.initTab(i); + GridTab gTab = gridWindow.getTab(i); + if (i == 0) { + gTab.setUpdateWindowContext(true); + gTab.setQuery(query); + gTab.query(false, 0, 0); + } else { + gTab.setUpdateWindowContext(false); + } + } + + GridTab gTab = gridWindow.getTab(0); + assertTrue(gTab.getRowCount()==1, "GridTab Row Count is not 1. GridTab="+gTab.getName()); + + String name = (String) gTab.getValue("Name"); + MBPartner bpartner = new MBPartner(Env.getCtx(), BP_JOE_BLOCK, getTrxName()); + assertTrue(bpartner.getName().equals(name), "GridTab Name != MBPartner.getName(). GridTab.Name="+name + " MBPartner.getName="+bpartner.getName()); + + //use trx to perform update + gTab.getTableModel().setImportingMode(true, getTrxName()); + String updateValue = "testUpdate"; + gTab.setValue(MBPartner.COLUMNNAME_Description, updateValue); + gTab.dataSave(true); + + //verify update is working + bpartner.load(getTrxName()); + String description = (String) gTab.getValue(MBPartner.COLUMNNAME_Description); + assertTrue(updateValue.equals(description), "GridTab Description != Update Value. GridTab.Description="+description); + assertTrue(bpartner.getDescription().equals(description), "GridTab Description != MBPartner.getDescription(). GridTab.Description="+description+ " MBPartner.getDescription="+bpartner.getDescription()); + } +}