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 delegateClass = (Class) 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); + } +}