diff --git a/org.adempiere.base/OSGI-INF/org.adempiere.base.MappedColumnCalloutFactory.xml b/org.adempiere.base/OSGI-INF/org.adempiere.base.MappedColumnCalloutFactory.xml
new file mode 100644
index 0000000000..339819c2c8
--- /dev/null
+++ b/org.adempiere.base/OSGI-INF/org.adempiere.base.MappedColumnCalloutFactory.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.adempiere.base/src/org/adempiere/base/Core.java b/org.adempiere.base/src/org/adempiere/base/Core.java
index 56517f4067..aae7cf8a49 100644
--- a/org.adempiere.base/src/org/adempiere/base/Core.java
+++ b/org.adempiere.base/src/org/adempiere/base/Core.java
@@ -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>> s_columnCalloutFactoryCache = new CCache<>(null, "List", 100, false);
+ private static final CCache>> s_columnCalloutFactoryNegativeCache = new CCache<>(null, "List Negative", 100, false);
/**
*
@@ -116,49 +118,61 @@ public class Core {
List list = new ArrayList();
String cacheKey = tableName + "." + columnName;
- List> cache = s_columnCalloutFactoryCache.get(cacheKey);
+ 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) {
- boolean staleReference = false;
- for (IServiceReferenceHolder factory : cache) {
- IColumnCalloutFactory service = factory.getService();
+ 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);
}
- } else {
- staleReference = true;
- break;
+ positiveReferenceHolders.add(referenceHolder);
+ } else {
+ negativeServiceReferences.add(referenceHolder.getServiceReference());
}
- } else {
- staleReference = true;
- break;
}
}
- if (!staleReference)
- return list;
- else
- s_columnCalloutFactoryCache.remove(cacheKey);
}
- List> factories = Service.locator().list(IColumnCalloutFactory.class).getServiceReferences();
- List> found = new ArrayList<>();
- if (factories != null) {
- for(IServiceReferenceHolder factory : factories) {
- IColumnCalloutFactory service = factory.getService();
+ 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);
}
- 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 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;
+ }
}
diff --git a/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java b/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java
new file mode 100644
index 0000000000..a2246fe52c
--- /dev/null
+++ b/org.adempiere.base/src/org/adempiere/base/IMappedColumnCalloutFactory.java
@@ -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 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 supplier);
+
+}
\ No newline at end of file
diff --git a/org.adempiere.base/src/org/adempiere/base/MappedColumnCalloutFactory.java b/org.adempiere.base/src/org/adempiere/base/MappedColumnCalloutFactory.java
new file mode 100644
index 0000000000..5d52a60000
--- /dev/null
+++ b/org.adempiere.base/src/org/adempiere/base/MappedColumnCalloutFactory.java
@@ -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>> calloutMap = new HashMap<>();
+
+ /**
+ * default constructor
+ */
+ public MappedColumnCalloutFactory() {
+ }
+
+ @Override
+ public IColumnCallout[] getColumnCallouts(String tableName, String columnName) {
+ List calloutList = new ArrayList();
+ 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> 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 supplier) {
+ StringBuilder key = new StringBuilder();
+ key.append(tableName.toLowerCase()).append("|").append(columnName.toLowerCase());
+ synchronized (calloutMap) {
+ List> list = calloutMap.get(key.toString());
+ if (list == null) {
+ list = new ArrayList>();
+ calloutMap.put(key.toString(), list);
+ }
+ list.add(supplier);
+ }
+ }
+
+ @Override
+ public void removeMapping(String tableName, String columnName, Supplier supplier) {
+ StringBuilder key = new StringBuilder();
+ key.append(tableName.toLowerCase()).append("|").append(columnName.toLowerCase());
+ synchronized (calloutMap) {
+ List> list = calloutMap.get(key.toString());
+ if (list != null) {
+ list.remove(supplier);
+ }
+ }
+ }
+}
diff --git a/org.idempiere.test/src/org/idempiere/test/model/MappedColumnCalloutFactoryTest.java b/org.idempiere.test/src/org/idempiere/test/model/MappedColumnCalloutFactoryTest.java
new file mode 100644
index 0000000000..033ad87402
--- /dev/null
+++ b/org.idempiere.test/src/org/idempiere/test/model/MappedColumnCalloutFactoryTest.java
@@ -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 properties = new Hashtable();
+ 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;
+ }
+
+ }
+}