diff --git a/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedEventManager.xml b/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedEventManager.xml
index e5b18b745e..68779f637d 100644
--- a/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedEventManager.xml
+++ b/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedEventManager.xml
@@ -1,4 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/org.adempiere.base/src/org/adempiere/base/AnnotationBasedEventManager.java b/org.adempiere.base/src/org/adempiere/base/AnnotationBasedEventManager.java
index 311baca451..67c9be31d2 100644
--- a/org.adempiere.base/src/org/adempiere/base/AnnotationBasedEventManager.java
+++ b/org.adempiere.base/src/org/adempiere/base/AnnotationBasedEventManager.java
@@ -27,6 +27,7 @@ package org.adempiere.base;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Level;
@@ -73,9 +74,9 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
private static final CLogger s_log = CLogger.getCLogger(AnnotationBasedEventManager.class);
- private IEventManager eventManager;
- private BundleContext bundleContext;
- private List handlers = new ArrayList<>();
+ protected IEventManager eventManager;
+ protected BundleContext bundleContext;
+ protected List handlers = new ArrayList<>();
private ServiceTracker serviceTracker;
@@ -137,50 +138,84 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
}
/**
- * Perform scan, discover and register of annotated classes
+ * Scan, discover and register annotated event delegate classes.
+ * The scan is asynchronous and return {@link CompletableFuture} to caller.
+ * If needed, caller can use the return {@link CompletableFuture} to wait for the scan to complete (using either get or join).
+ * @param context bundle context
+ * @param packageNames one or more package to scan
+ * @return CompletableFuture>
*/
- protected void scan() {
- long start = System.currentTimeMillis();
- ClassLoader classLoader = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
-
+ public synchronized CompletableFuture> scan(BundleContext context, String ...packageNames) {
+ return scan(context, false, packageNames);
+ }
+
+ /**
+ * Scan, discover and register annotated event delegate classes.
+ * @param context bundle context
+ * @param logScanDuration
+ * @param packageNames one or more package to scan
+ * @return CompletableFuture>
+ */
+ protected CompletableFuture> scan(BundleContext context, boolean logScanDuration, String ...packageNames) {
+ long start = logScanDuration ? System.currentTimeMillis() : 0;
+ final CompletableFuture> completable = new CompletableFuture<>();
+ ClassLoader classLoader = context.getBundle().adapt(BundleWiring.class).getClassLoader();
+
ClassGraph graph = new ClassGraph()
.enableAnnotationInfo()
.overrideClassLoaders(classLoader)
.disableNestedJarScanning()
.disableModuleScanning()
- .acceptPackagesNonRecursive(getPackages());
+ .acceptPackagesNonRecursive(packageNames);
ScanResultProcessor scanResultProcessor = scanResult ->
{
+ List handlerList = new ArrayList<>();
for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(EventTopicDelegate.class)) {
if (classInfo.isAbstract())
continue;
+ EventHandler handler = null;
String className = classInfo.getName();
AnnotationInfo baseInfo = classInfo.getAnnotationInfo(EventTopicDelegate.class);
String filter = (String) baseInfo.getParameterValues().getValue("filter");
if (classInfo.hasAnnotation(ModelEventTopic.class)) {
AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(ModelEventTopic.class);
- modelEventDelegate(classLoader, className, annotationInfo, filter);
+ handler = modelEventDelegate(classLoader, className, annotationInfo, filter);
} else if (classInfo.hasAnnotation(ImportEventTopic.class)) {
AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(ImportEventTopic.class);
- importEventDelegate(classLoader, className, annotationInfo, filter);
+ handler = importEventDelegate(classLoader, className, annotationInfo, filter);
} else if (classInfo.hasAnnotation(ProcessEventTopic.class)) {
AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(ProcessEventTopic.class);
- processEventDelegate(classLoader, className, annotationInfo, filter);
+ handler = processEventDelegate(classLoader, className, annotationInfo, filter);
} else {
- simpleEventDelegate(classLoader, className, filter);
+ handler = simpleEventDelegate(classLoader, className, filter);
}
+ if (handler != null)
+ handlerList.add(handler);
}
long end = System.currentTimeMillis();
- s_log.info(() -> this.getClass().getSimpleName() + " loaded " + handlers.size() + " classes in "
- + ((end-start)/1000f) + "s");
- signalScanCompletion(true);
+ if (logScanDuration)
+ s_log.info(() -> this.getClass().getSimpleName() + " loaded " + handlerList.size() + " classes in "
+ + ((end-start)/1000f) + "s");
+ if (handlerList.size() > 0) {
+ synchronized (handlers) {
+ handlers.addAll(handlerList);
+ }
+ }
+ completable.complete(handlerList);
};
graph.scanAsync(getExecutorService(), getMaxThreads(), scanResultProcessor, getScanFailureHandler());
+ return completable;
+ }
+ /**
+ * Perform asynchronous scan, discover and register of annotated event delegate classes.
+ */
+ protected void scan() {
+ scan(bundleContext, true, getPackages());
}
- private void simpleEventDelegate(ClassLoader classLoader, String className, String filter) {
+ private EventHandler simpleEventDelegate(ClassLoader classLoader, String className, String filter) {
try {
@SuppressWarnings("unchecked")
Class extends EventDelegate> delegateClass = (Class extends EventDelegate>) classLoader.loadClass(className);
@@ -189,15 +224,16 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
SimpleEventHandler handler = new SimpleEventHandler(delegateClass, supplier);
if (!Util.isEmpty(filter, true))
handler.setFilter(filter);
- handlers.add(handler);
- eventManager.register(handler.getTopics(), handler.getFilter(), handler);
+ eventManager.register(handler.getTopics(), handler.getFilter(), handler);
+ return handler;
} catch (Exception e) {
if (s_log.isLoggable(Level.INFO))
s_log.log(Level.INFO, e.getMessage(), e);
+ return null;
}
}
- private void processEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) {
+ private EventHandler processEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) {
try {
String processUUID = (String) annotationInfo.getParameterValues().getValue("processUUID");
@SuppressWarnings("unchecked")
@@ -207,15 +243,16 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
ProcessEventHandler handler = new ProcessEventHandler(delegateClass, processUUID, supplier);
if (!Util.isEmpty(filter, true))
handler.setFilter(filter);
- handlers.add(handler);
eventManager.register(handler.getTopics(), handler.getFilter(), handler);
+ return handler;
} catch (Exception e) {
if (s_log.isLoggable(Level.INFO))
s_log.log(Level.INFO, e.getMessage(), e);
+ return null;
}
}
- private void importEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) {
+ private EventHandler importEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) {
try {
String importTableName = (String) annotationInfo.getParameterValues().getValue("importTableName");
@SuppressWarnings("unchecked")
@@ -225,15 +262,16 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
ImportEventHandler handler = new ImportEventHandler(delegateClass, importTableName, supplier);
if (!Util.isEmpty(filter, true))
handler.setFilter(filter);
- handlers.add(handler);
- eventManager.register(handler.getTopics(), handler.getFilter(), handler);
+ eventManager.register(handler.getTopics(), handler.getFilter(), handler);
+ return handler;
} catch (Exception e) {
if (s_log.isLoggable(Level.INFO))
s_log.log(Level.INFO, e.getMessage(), e);
+ return null;
}
}
- private void modelEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) {
+ private EventHandler modelEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) {
try {
AnnotationClassRef classRef = (AnnotationClassRef) annotationInfo.getParameterValues().getValue("modelClass");
@SuppressWarnings("unchecked")
@@ -246,11 +284,12 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
ModelEventHandler> handler = new ModelEventHandler(modelClass, delegateClass, supplier);
if (!Util.isEmpty(filter, true))
handler.setFilter(filter);
- handlers.add(handler);
eventManager.register(handler.getTopics(), handler.getFilter(), handler);
+ return handler;
} catch (Exception e) {
if (s_log.isLoggable(Level.INFO))
s_log.log(Level.INFO, e.getMessage(), e);
+ return null;
}
}
diff --git a/org.adempiere.base/src/org/adempiere/base/Core.java b/org.adempiere.base/src/org/adempiere/base/Core.java
index fe9ab7ca84..2a76748d04 100644
--- a/org.adempiere.base/src/org/adempiere/base/Core.java
+++ b/org.adempiere.base/src/org/adempiere/base/Core.java
@@ -1076,4 +1076,15 @@ public class Core {
//fall back, should not reach here
return new DefaultTaxLookup();
}
+
+ /**
+ * @return {@link DefaultAnnotationBasedEventManager}
+ */
+ public static DefaultAnnotationBasedEventManager getDefaultAnnotationBasedEventManager() {
+ IServiceReferenceHolder serviceReference = Service.locator().locate(DefaultAnnotationBasedEventManager.class).getServiceReference();
+ if (serviceReference != null) {
+ return serviceReference.getService();
+ }
+ return null;
+ }
}
diff --git a/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedEventManager.java b/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedEventManager.java
index 369a75956e..5005464de1 100644
--- a/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedEventManager.java
+++ b/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedEventManager.java
@@ -26,7 +26,7 @@ package org.adempiere.base;
import org.osgi.service.component.annotations.Component;
-@Component(immediate = true, service = {})
+@Component(immediate = true, service = {DefaultAnnotationBasedEventManager.class})
public class DefaultAnnotationBasedEventManager extends AnnotationBasedEventManager {
/**
diff --git a/org.idempiere.test/src/org/idempiere/test/event/EventDelegateAnnotationTest.java b/org.idempiere.test/src/org/idempiere/test/event/EventDelegateAnnotationTest.java
new file mode 100644
index 0000000000..cd1a35414b
--- /dev/null
+++ b/org.idempiere.test/src/org/idempiere/test/event/EventDelegateAnnotationTest.java
@@ -0,0 +1,65 @@
+/***********************************************************************
+ * 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.event;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.adempiere.base.Core;
+import org.adempiere.base.DefaultAnnotationBasedEventManager;
+import org.compiere.model.MTest;
+import org.compiere.util.Env;
+import org.idempiere.test.AbstractTestCase;
+import org.idempiere.test.TestActivator;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Isolated;
+import org.osgi.service.event.EventHandler;
+
+/**
+ * @author hengsin
+ */
+@Isolated
+public class EventDelegateAnnotationTest extends AbstractTestCase {
+
+ public EventDelegateAnnotationTest() {
+ }
+
+ @Test
+ public void testAnnotatedEventDelegate() {
+ DefaultAnnotationBasedEventManager mgr = Core.getDefaultAnnotationBasedEventManager();
+ CompletableFuture> completable = mgr.scan(TestActivator.context, MTestEventDelegate.class.getPackageName());
+ completable.join();
+
+ String desc = "test";
+ MTest mtest = new MTest(Env.getCtx(), 0, getTrxName());
+ mtest.setName("testAnnotatedEventDelegate");
+ mtest.setDescription(desc);
+ mtest.saveEx();
+
+ assertEquals(desc + "MTestEventDelegate", mtest.getDescription(), "MTestEventDelegate not handling before new event as expected");
+ }
+}
diff --git a/org.idempiere.test/src/org/idempiere/test/event/MTestEventDelegate.java b/org.idempiere.test/src/org/idempiere/test/event/MTestEventDelegate.java
new file mode 100644
index 0000000000..130632c366
--- /dev/null
+++ b/org.idempiere.test/src/org/idempiere/test/event/MTestEventDelegate.java
@@ -0,0 +1,60 @@
+/***********************************************************************
+ * 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.event;
+
+import org.adempiere.base.annotation.EventTopicDelegate;
+import org.adempiere.base.annotation.ModelEventTopic;
+import org.adempiere.base.event.annotations.ModelEventDelegate;
+import org.adempiere.base.event.annotations.po.BeforeChange;
+import org.adempiere.base.event.annotations.po.BeforeNew;
+import org.compiere.model.MTest;
+import org.osgi.service.event.Event;
+
+/**
+ * @author hengsin
+ */
+@EventTopicDelegate
+@ModelEventTopic(modelClass = MTest.class)
+public class MTestEventDelegate extends ModelEventDelegate {
+
+ /**
+ * @param po
+ * @param event
+ */
+ public MTestEventDelegate(MTest po, Event event) {
+ super(po, event);
+ }
+
+ @BeforeChange
+ @BeforeNew
+ public void onBeforeChange() {
+ String desc = getModel().getDescription();
+ if (desc != null)
+ desc = desc + "MTestEventDelegate";
+ else
+ desc = "MTestEventDelegate";
+ getModel().setDescription(desc);
+ }
+}