IDEMPIERE-4690 Add column callout factory base class backed by Map and Lambda functional object (#587)

Refactoring. Add unit test for gridtab and callout.
This commit is contained in:
hengsin 2021-02-16 18:20:42 +08:00 committed by GitHub
parent c91a13ee88
commit 4b20720932
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 379 additions and 109 deletions

View File

@ -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<String, IServiceReferenceHolder<ICalloutFactory>> s_calloutFactoryCache = new CCache<>(ICALLOUT_FACTORY_CACHE_TABLE_NAME, "ICalloutFactory", 100, false);
private static final CCache<String, List<IServiceReferenceHolder<IColumnCalloutFactory>>> s_columnCalloutFactoryCache = new CCache<>(LIST_ICOLUMNCALLOUT_FACTORY_CACHE_TABLE_NAME, "List<IColumnCalloutFactory>", 100, false);
private static final CCache<String, List<ServiceReference<IColumnCalloutFactory>>> s_columnCalloutFactoryNegativeCache = new CCache<>(LIST_ICOLUMNCALLOUT_FACTORY_CACHE_TABLE_NAME, "List<IColumnCalloutFactory> Negative", 100, false);
private static IServiceReferenceHolder<IMappedColumnCalloutFactory> s_mappedColumnCalloutFactoryReference = null;
private ColumnCalloutManager() {
}
/**
*
* @param tableName
* @param columnName
* @return list of {@link IColumnCallout} register for tableName.columnName
*/
public static List<IColumnCallout> findCallout(String tableName, String columnName) {
List<IColumnCallout> list = new ArrayList<IColumnCallout>();
String cacheKey = tableName + "." + columnName;
List<IServiceReferenceHolder<IColumnCalloutFactory>> cache = s_columnCalloutFactoryCache.get(cacheKey);
List<ServiceReference<IColumnCalloutFactory>> negativeCache = s_columnCalloutFactoryNegativeCache.get(cacheKey);
List<ServiceReference<IColumnCalloutFactory>> negativeServiceReferences = new ArrayList<ServiceReference<IColumnCalloutFactory>>();
if (negativeCache != null) {
negativeServiceReferences.addAll(negativeCache);
}
List<ServiceReference<IColumnCalloutFactory>> cacheReferences = new ArrayList<ServiceReference<IColumnCalloutFactory>>();
List<IServiceReferenceHolder<IColumnCalloutFactory>> positiveReferenceHolders = new ArrayList<>();
if (cache != null) {
for (IServiceReferenceHolder<IColumnCalloutFactory> 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<IServiceReferenceHolder<IColumnCalloutFactory>> referenceHolders = Service.locator().list(IColumnCalloutFactory.class).getServiceReferences();
if (referenceHolders != null) {
for(IServiceReferenceHolder<IColumnCalloutFactory> referenceHolder : referenceHolders) {
ServiceReference<IColumnCalloutFactory> 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<ICalloutFactory> 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<IServiceReferenceHolder<ICalloutFactory>> factories = Service.locator().list(ICalloutFactory.class).getServiceReferences();
if (factories != null) {
for(IServiceReferenceHolder<ICalloutFactory> 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<IMappedColumnCalloutFactory> serviceReference = Service.locator().locate(IMappedColumnCalloutFactory.class).getServiceReference();
if (serviceReference != null) {
factoryService = serviceReference.getService();
s_mappedColumnCalloutFactoryReference = serviceReference;
}
return factoryService;
}
}

View File

@ -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<String, List<IServiceReferenceHolder<IColumnCalloutFactory>>> s_columnCalloutFactoryCache = new CCache<>(null, "List<IColumnCalloutFactory>", 100, false);
private static final CCache<String, List<ServiceReference<IColumnCalloutFactory>>> s_columnCalloutFactoryNegativeCache = new CCache<>(null, "List<IColumnCalloutFactory> Negative", 100, false);
/**
*
* @param tableName
@ -116,70 +111,9 @@ public class Core {
* @return list of callout register for tableName.columnName
*/
public static List<IColumnCallout> findCallout(String tableName, String columnName) {
List<IColumnCallout> list = new ArrayList<IColumnCallout>();
String cacheKey = tableName + "." + columnName;
List<IServiceReferenceHolder<IColumnCalloutFactory>> cache = s_columnCalloutFactoryCache.get(cacheKey);
List<ServiceReference<IColumnCalloutFactory>> negativeCache = s_columnCalloutFactoryNegativeCache.get(cacheKey);
List<ServiceReference<IColumnCalloutFactory>> negativeServiceReferences = new ArrayList<ServiceReference<IColumnCalloutFactory>>();
if (negativeCache != null) {
negativeServiceReferences.addAll(negativeCache);
}
List<ServiceReference<IColumnCalloutFactory>> cacheReferences = new ArrayList<ServiceReference<IColumnCalloutFactory>>();
List<IServiceReferenceHolder<IColumnCalloutFactory>> positiveReferenceHolders = new ArrayList<>();
if (cache != null) {
for (IServiceReferenceHolder<IColumnCalloutFactory> 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());
}
}
}
return ColumnCalloutManager.findCallout(tableName, columnName);
}
int positiveAdded = 0;
int negativeAdded = 0;
List<IServiceReferenceHolder<IColumnCalloutFactory>> referenceHolders = Service.locator().list(IColumnCalloutFactory.class).getServiceReferences();
if (referenceHolders != null) {
for(IServiceReferenceHolder<IColumnCalloutFactory> 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;
}
private static final CCache<String, IServiceReferenceHolder<ICalloutFactory>> 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<ICalloutFactory> 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<IServiceReferenceHolder<ICalloutFactory>> factories = Service.locator().list(ICalloutFactory.class).getServiceReferences();
if (factories != null) {
for(IServiceReferenceHolder<ICalloutFactory> 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<String, IServiceReferenceHolder<IProcessFactory>> s_processFactoryCache = new CCache<>(null, "IProcessFactory", 100, false);
@ -983,25 +892,12 @@ public class Core {
return processFactoryService;
}
private static IServiceReferenceHolder<IMappedColumnCalloutFactory> 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<IMappedColumnCalloutFactory> serviceReference = Service.locator().locate(IMappedColumnCalloutFactory.class).getServiceReference();
if (serviceReference != null) {
factoryService = serviceReference.getService();
s_mappedColumnCalloutFactoryReference = serviceReference;
}
return factoryService;
return ColumnCalloutManager.getMappedColumnCalloutFactory();
}
private static IServiceReferenceHolder<IMappedDocumentFactory> s_mappedDocumentFactoryReference = null;

View File

@ -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<IColumnCallout> supplier);
public void addMapping(String tableName, String columnName, Supplier<IColumnCallout> 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<IColumnCallout> supplier);
public void removeMapping(String tableName, String columnName, Supplier<IColumnCallout> supplier);
}

View File

@ -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());
}
}