IDEMPIERE-4690 Add column callout factory base class backed by Map an… (#567)
* IDEMPIERE-4690 Add column callout factory base class backed by Map and Lambda functional object Fix column callout factory cache. Co-authored-by: Carlos Ruiz <carg67@gmail.com>
This commit is contained in:
parent
1c5a28aa0a
commit
ddd1c40eb2
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.adempiere.base.MappedColumnCalloutFactory">
|
||||||
|
<property name="service.ranking" type="Integer" value="1"/>
|
||||||
|
<service>
|
||||||
|
<provide interface="org.adempiere.base.IColumnCalloutFactory"/>
|
||||||
|
<provide interface="org.adempiere.base.IMappedColumnCalloutFactory"/>
|
||||||
|
</service>
|
||||||
|
<implementation class="org.adempiere.base.MappedColumnCalloutFactory"/>
|
||||||
|
</scr:component>
|
|
@ -57,6 +57,7 @@ import org.idempiere.fa.service.api.IDepreciationMethod;
|
||||||
import org.idempiere.fa.service.api.IDepreciationMethodFactory;
|
import org.idempiere.fa.service.api.IDepreciationMethodFactory;
|
||||||
import org.idempiere.model.IMappedModelFactory;
|
import org.idempiere.model.IMappedModelFactory;
|
||||||
import org.idempiere.process.IMappedProcessFactory;
|
import org.idempiere.process.IMappedProcessFactory;
|
||||||
|
import org.osgi.framework.ServiceReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a facade class for the Service Locator.
|
* This is a facade class for the Service Locator.
|
||||||
|
@ -105,6 +106,7 @@ 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<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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -117,48 +119,60 @@ public class Core {
|
||||||
|
|
||||||
String cacheKey = tableName + "." + columnName;
|
String cacheKey = tableName + "." + columnName;
|
||||||
List<IServiceReferenceHolder<IColumnCalloutFactory>> cache = s_columnCalloutFactoryCache.get(cacheKey);
|
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) {
|
if (cache != null) {
|
||||||
boolean staleReference = false;
|
for (IServiceReferenceHolder<IColumnCalloutFactory> referenceHolder : cache) {
|
||||||
for (IServiceReferenceHolder<IColumnCalloutFactory> factory : cache) {
|
cacheReferences.add(referenceHolder.getServiceReference());
|
||||||
IColumnCalloutFactory service = factory.getService();
|
IColumnCalloutFactory service = referenceHolder.getService();
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
IColumnCallout[] callouts = service.getColumnCallouts(tableName, columnName);
|
IColumnCallout[] callouts = service.getColumnCallouts(tableName, columnName);
|
||||||
if (callouts != null && callouts.length > 0) {
|
if (callouts != null && callouts.length > 0) {
|
||||||
for(IColumnCallout callout : callouts) {
|
for(IColumnCallout callout : callouts) {
|
||||||
list.add(callout);
|
list.add(callout);
|
||||||
}
|
}
|
||||||
|
positiveReferenceHolders.add(referenceHolder);
|
||||||
} else {
|
} else {
|
||||||
staleReference = true;
|
negativeServiceReferences.add(referenceHolder.getServiceReference());
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
staleReference = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!staleReference)
|
|
||||||
return list;
|
|
||||||
else
|
|
||||||
s_columnCalloutFactoryCache.remove(cacheKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IServiceReferenceHolder<IColumnCalloutFactory>> factories = Service.locator().list(IColumnCalloutFactory.class).getServiceReferences();
|
int positiveAdded = 0;
|
||||||
List<IServiceReferenceHolder<IColumnCalloutFactory>> found = new ArrayList<>();
|
int negativeAdded = 0;
|
||||||
if (factories != null) {
|
List<IServiceReferenceHolder<IColumnCalloutFactory>> referenceHolders = Service.locator().list(IColumnCalloutFactory.class).getServiceReferences();
|
||||||
for(IServiceReferenceHolder<IColumnCalloutFactory> factory : factories) {
|
if (referenceHolders != null) {
|
||||||
IColumnCalloutFactory service = factory.getService();
|
for(IServiceReferenceHolder<IColumnCalloutFactory> referenceHolder : referenceHolders) {
|
||||||
|
if (cacheReferences.contains(referenceHolder.getServiceReference()) || negativeServiceReferences.contains(referenceHolder.getServiceReference()))
|
||||||
|
continue;
|
||||||
|
IColumnCalloutFactory service = referenceHolder.getService();
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
IColumnCallout[] callouts = service.getColumnCallouts(tableName, columnName);
|
IColumnCallout[] callouts = service.getColumnCallouts(tableName, columnName);
|
||||||
if (callouts != null && callouts.length > 0) {
|
if (callouts != null && callouts.length > 0) {
|
||||||
for(IColumnCallout callout : callouts) {
|
for(IColumnCallout callout : callouts) {
|
||||||
list.add(callout);
|
list.add(callout);
|
||||||
}
|
}
|
||||||
found.add(factory);
|
positiveReferenceHolders.add(referenceHolder);
|
||||||
|
positiveAdded++;
|
||||||
|
} else {
|
||||||
|
negativeServiceReferences.add(referenceHolder.getServiceReference());
|
||||||
|
negativeAdded++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s_columnCalloutFactoryCache.put(cacheKey, found);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -967,4 +981,25 @@ public class Core {
|
||||||
}
|
}
|
||||||
return processFactoryService;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/***********************************************************************
|
||||||
|
* 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.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hengsin
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface IMappedColumnCalloutFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add mapping for callout
|
||||||
|
* @param tableName case insensitive table name or * to match all table
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove mapping for callout
|
||||||
|
* @param tableName case insensitive table name or * to match all table
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/***********************************************************************
|
||||||
|
* 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.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hengsin
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component(name = "org.adempiere.base.MappedColumnCalloutFactory",
|
||||||
|
immediate = true,
|
||||||
|
service = {IColumnCalloutFactory.class, IMappedColumnCalloutFactory.class},
|
||||||
|
property = {"service.ranking:Integer=1"})
|
||||||
|
public class MappedColumnCalloutFactory implements IColumnCalloutFactory, IMappedColumnCalloutFactory {
|
||||||
|
|
||||||
|
private final HashMap<String, List<Supplier<IColumnCallout>>> calloutMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default constructor
|
||||||
|
*/
|
||||||
|
public MappedColumnCalloutFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IColumnCallout[] getColumnCallouts(String tableName, String columnName) {
|
||||||
|
List<IColumnCallout> calloutList = new ArrayList<IColumnCallout>();
|
||||||
|
StringBuilder key = new StringBuilder();
|
||||||
|
key.append(tableName.toLowerCase()).append("|").append(columnName.toLowerCase());
|
||||||
|
StringBuilder key1 = new StringBuilder();
|
||||||
|
key1.append("*|").append(columnName.toLowerCase());
|
||||||
|
StringBuilder key2 = new StringBuilder();
|
||||||
|
key2.append(tableName.toLowerCase()).append("|*");
|
||||||
|
synchronized (calloutMap) {
|
||||||
|
List<Supplier<IColumnCallout>> list = calloutMap.get(key.toString());
|
||||||
|
if (list != null && list.size() > 0) {
|
||||||
|
list.forEach(e -> calloutList.add(e.get()));
|
||||||
|
}
|
||||||
|
list = calloutMap.get(key1.toString());
|
||||||
|
if (list != null && list.size() > 0) {
|
||||||
|
list.forEach(e -> calloutList.add(e.get()));
|
||||||
|
}
|
||||||
|
list = calloutMap.get(key2.toString());
|
||||||
|
if (list != null && list.size() > 0) {
|
||||||
|
list.forEach(e -> calloutList.add(e.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return calloutList.isEmpty() ? null : calloutList.toArray(new IColumnCallout[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addMapping(String tableName, String columnName, Supplier<IColumnCallout> supplier) {
|
||||||
|
StringBuilder key = new StringBuilder();
|
||||||
|
key.append(tableName.toLowerCase()).append("|").append(columnName.toLowerCase());
|
||||||
|
synchronized (calloutMap) {
|
||||||
|
List<Supplier<IColumnCallout>> list = calloutMap.get(key.toString());
|
||||||
|
if (list == null) {
|
||||||
|
list = new ArrayList<Supplier<IColumnCallout>>();
|
||||||
|
calloutMap.put(key.toString(), list);
|
||||||
|
}
|
||||||
|
list.add(supplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeMapping(String tableName, String columnName, Supplier<IColumnCallout> supplier) {
|
||||||
|
StringBuilder key = new StringBuilder();
|
||||||
|
key.append(tableName.toLowerCase()).append("|").append(columnName.toLowerCase());
|
||||||
|
synchronized (calloutMap) {
|
||||||
|
List<Supplier<IColumnCallout>> list = calloutMap.get(key.toString());
|
||||||
|
if (list != null) {
|
||||||
|
list.remove(supplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/***********************************************************************
|
||||||
|
* This file is part of iDempiere ERP Open Source *
|
||||||
|
* http://www.idempiere.org *
|
||||||
|
* *
|
||||||
|
* Copyright (C) Contributors *
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU General Public License *
|
||||||
|
* as published by the Free Software Foundation; either version 2 *
|
||||||
|
* of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This program is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU General Public License *
|
||||||
|
* along with this program; if not, write to the Free Software *
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||||
|
* MA 02110-1301, USA. *
|
||||||
|
* *
|
||||||
|
* Contributors: *
|
||||||
|
* - hengsin *
|
||||||
|
**********************************************************************/
|
||||||
|
package org.idempiere.test.model;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Dictionary;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.adempiere.base.Core;
|
||||||
|
import org.adempiere.base.IColumnCallout;
|
||||||
|
import org.adempiere.base.IColumnCalloutFactory;
|
||||||
|
import org.adempiere.base.MappedColumnCalloutFactory;
|
||||||
|
import org.compiere.model.GridField;
|
||||||
|
import org.compiere.model.GridTab;
|
||||||
|
import org.compiere.model.MTest;
|
||||||
|
import org.idempiere.test.AbstractTestCase;
|
||||||
|
import org.idempiere.test.TestActivator;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||||
|
import org.osgi.framework.BundleContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hengsin
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@TestMethodOrder(OrderAnnotation.class)
|
||||||
|
public class MappedColumnCalloutFactoryTest extends AbstractTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default constructor
|
||||||
|
*/
|
||||||
|
public MappedColumnCalloutFactoryTest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
public void testDefaultMappedColumnCalloutFactory() {
|
||||||
|
var factory = Core.getMappedColumnCalloutFactory();
|
||||||
|
factory.addMapping(MTest.Table_Name, MTest.COLUMNNAME_T_Amount, () -> new MyTestAmountCallout());
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
public void testCustomMappedColumnCalloutFactory() {
|
||||||
|
BundleContext bc = TestActivator.context;
|
||||||
|
Dictionary<String, Object> properties = new Hashtable<String, Object>();
|
||||||
|
properties.put("service.ranking", Integer.valueOf(1));
|
||||||
|
bc.registerService(IColumnCalloutFactory.class, new MyFactory(), properties);
|
||||||
|
var list = Core.findCallout(MTest.Table_Name, MTest.COLUMNNAME_T_Amount);
|
||||||
|
var optional = list.stream().filter(e -> e instanceof MyTestAmountCallout2).findFirst();
|
||||||
|
assertTrue(optional.isPresent(), "Can't find MyTestAmountCallout2 column callout for " + MTest.Table_Name + "." + MTest.COLUMNNAME_T_Amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static class MyFactory extends MappedColumnCalloutFactory {
|
||||||
|
public MyFactory() {
|
||||||
|
addMapping(MTest.Table_Name, MTest.COLUMNNAME_T_Amount, () -> new MyTestAmountCallout2());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static class MyTestAmountCallout implements IColumnCallout {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String start(Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value, Object oldValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static class MyTestAmountCallout2 implements IColumnCallout {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String start(Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value, Object oldValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue