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:
hengsin 2021-02-06 21:53:59 +08:00 committed by GitHub
parent 1c5a28aa0a
commit ddd1c40eb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 329 additions and 22 deletions

View File

@ -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>

View File

@ -57,6 +57,7 @@ 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.
@ -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<ServiceReference<IColumnCalloutFactory>>> s_columnCalloutFactoryNegativeCache = new CCache<>(null, "List<IColumnCalloutFactory> Negative", 100, false);
/**
*
@ -117,48 +119,60 @@ public class Core {
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) {
boolean staleReference = false;
for (IServiceReferenceHolder<IColumnCalloutFactory> factory : cache) {
IColumnCalloutFactory service = factory.getService();
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 {
staleReference = true;
break;
negativeServiceReferences.add(referenceHolder.getServiceReference());
}
} else {
staleReference = true;
break;
}
}
if (!staleReference)
return list;
else
s_columnCalloutFactoryCache.remove(cacheKey);
}
List<IServiceReferenceHolder<IColumnCalloutFactory>> factories = Service.locator().list(IColumnCalloutFactory.class).getServiceReferences();
List<IServiceReferenceHolder<IColumnCalloutFactory>> found = new ArrayList<>();
if (factories != null) {
for(IServiceReferenceHolder<IColumnCalloutFactory> factory : factories) {
IColumnCalloutFactory service = factory.getService();
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);
}
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;
}
@ -967,4 +981,25 @@ 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;
}
}

View File

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

View File

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

View File

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