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