IDEMPIERE-5600 Add Core.getDefaultAnnotationBasedEventManager() API (#1698)

* IDEMPIERE-5600 Add Core.getDefaultAnnotationBasedEventManager() API

* IDEMPIERE-5600 Add Core.getDefaultAnnotationBasedEventManager() API

- minor refinement
This commit is contained in:
hengsin 2023-03-07 03:40:48 +08:00 committed by GitHub
parent 7417e1ce3c
commit 859dd1a723
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 205 additions and 27 deletions

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?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.DefaultAnnotationBasedEventManager"> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.adempiere.base.DefaultAnnotationBasedEventManager">
<service>
<provide interface="org.adempiere.base.DefaultAnnotationBasedEventManager"/>
</service>
<implementation class="org.adempiere.base.DefaultAnnotationBasedEventManager"/> <implementation class="org.adempiere.base.DefaultAnnotationBasedEventManager"/>
</scr:component> </scr:component>

View File

@ -27,6 +27,7 @@ package org.adempiere.base;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.logging.Level; 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 static final CLogger s_log = CLogger.getCLogger(AnnotationBasedEventManager.class);
private IEventManager eventManager; protected IEventManager eventManager;
private BundleContext bundleContext; protected BundleContext bundleContext;
private List<EventHandler> handlers = new ArrayList<>(); protected List<EventHandler> handlers = new ArrayList<>();
private ServiceTracker<IEventManager, IEventManager> serviceTracker; private ServiceTracker<IEventManager, IEventManager> 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. <br/>
* 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<List<EventHandler>>
*/ */
protected void scan() { public synchronized CompletableFuture<List<EventHandler>> scan(BundleContext context, String ...packageNames) {
long start = System.currentTimeMillis(); return scan(context, false, packageNames);
ClassLoader classLoader = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader(); }
/**
* Scan, discover and register annotated event delegate classes.
* @param context bundle context
* @param logScanDuration
* @param packageNames one or more package to scan
* @return CompletableFuture<List<EventHandler>>
*/
protected CompletableFuture<List<EventHandler>> scan(BundleContext context, boolean logScanDuration, String ...packageNames) {
long start = logScanDuration ? System.currentTimeMillis() : 0;
final CompletableFuture<List<EventHandler>> completable = new CompletableFuture<>();
ClassLoader classLoader = context.getBundle().adapt(BundleWiring.class).getClassLoader();
ClassGraph graph = new ClassGraph() ClassGraph graph = new ClassGraph()
.enableAnnotationInfo() .enableAnnotationInfo()
.overrideClassLoaders(classLoader) .overrideClassLoaders(classLoader)
.disableNestedJarScanning() .disableNestedJarScanning()
.disableModuleScanning() .disableModuleScanning()
.acceptPackagesNonRecursive(getPackages()); .acceptPackagesNonRecursive(packageNames);
ScanResultProcessor scanResultProcessor = scanResult -> ScanResultProcessor scanResultProcessor = scanResult ->
{ {
List<EventHandler> handlerList = new ArrayList<>();
for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(EventTopicDelegate.class)) { for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(EventTopicDelegate.class)) {
if (classInfo.isAbstract()) if (classInfo.isAbstract())
continue; continue;
EventHandler handler = null;
String className = classInfo.getName(); String className = classInfo.getName();
AnnotationInfo baseInfo = classInfo.getAnnotationInfo(EventTopicDelegate.class); AnnotationInfo baseInfo = classInfo.getAnnotationInfo(EventTopicDelegate.class);
String filter = (String) baseInfo.getParameterValues().getValue("filter"); String filter = (String) baseInfo.getParameterValues().getValue("filter");
if (classInfo.hasAnnotation(ModelEventTopic.class)) { if (classInfo.hasAnnotation(ModelEventTopic.class)) {
AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(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)) { } else if (classInfo.hasAnnotation(ImportEventTopic.class)) {
AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(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)) { } else if (classInfo.hasAnnotation(ProcessEventTopic.class)) {
AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(ProcessEventTopic.class); AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(ProcessEventTopic.class);
processEventDelegate(classLoader, className, annotationInfo, filter); handler = processEventDelegate(classLoader, className, annotationInfo, filter);
} else { } else {
simpleEventDelegate(classLoader, className, filter); handler = simpleEventDelegate(classLoader, className, filter);
} }
if (handler != null)
handlerList.add(handler);
} }
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
s_log.info(() -> this.getClass().getSimpleName() + " loaded " + handlers.size() + " classes in " if (logScanDuration)
+ ((end-start)/1000f) + "s"); s_log.info(() -> this.getClass().getSimpleName() + " loaded " + handlerList.size() + " classes in "
signalScanCompletion(true); + ((end-start)/1000f) + "s");
if (handlerList.size() > 0) {
synchronized (handlers) {
handlers.addAll(handlerList);
}
}
completable.complete(handlerList);
}; };
graph.scanAsync(getExecutorService(), getMaxThreads(), scanResultProcessor, getScanFailureHandler()); 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 { try {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<? extends EventDelegate> delegateClass = (Class<? extends EventDelegate>) classLoader.loadClass(className); 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); SimpleEventHandler handler = new SimpleEventHandler(delegateClass, supplier);
if (!Util.isEmpty(filter, true)) if (!Util.isEmpty(filter, true))
handler.setFilter(filter); 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) { } catch (Exception e) {
if (s_log.isLoggable(Level.INFO)) if (s_log.isLoggable(Level.INFO))
s_log.log(Level.INFO, e.getMessage(), e); 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 { try {
String processUUID = (String) annotationInfo.getParameterValues().getValue("processUUID"); String processUUID = (String) annotationInfo.getParameterValues().getValue("processUUID");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -207,15 +243,16 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
ProcessEventHandler handler = new ProcessEventHandler(delegateClass, processUUID, supplier); ProcessEventHandler handler = new ProcessEventHandler(delegateClass, processUUID, supplier);
if (!Util.isEmpty(filter, true)) if (!Util.isEmpty(filter, true))
handler.setFilter(filter); 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) { } catch (Exception e) {
if (s_log.isLoggable(Level.INFO)) if (s_log.isLoggable(Level.INFO))
s_log.log(Level.INFO, e.getMessage(), e); 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 { try {
String importTableName = (String) annotationInfo.getParameterValues().getValue("importTableName"); String importTableName = (String) annotationInfo.getParameterValues().getValue("importTableName");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -225,15 +262,16 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
ImportEventHandler handler = new ImportEventHandler(delegateClass, importTableName, supplier); ImportEventHandler handler = new ImportEventHandler(delegateClass, importTableName, supplier);
if (!Util.isEmpty(filter, true)) if (!Util.isEmpty(filter, true))
handler.setFilter(filter); 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) { } catch (Exception e) {
if (s_log.isLoggable(Level.INFO)) if (s_log.isLoggable(Level.INFO))
s_log.log(Level.INFO, e.getMessage(), e); 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 { try {
AnnotationClassRef classRef = (AnnotationClassRef) annotationInfo.getParameterValues().getValue("modelClass"); AnnotationClassRef classRef = (AnnotationClassRef) annotationInfo.getParameterValues().getValue("modelClass");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -246,11 +284,12 @@ public abstract class AnnotationBasedEventManager extends AnnotationBasedFactory
ModelEventHandler<?> handler = new ModelEventHandler(modelClass, delegateClass, supplier); ModelEventHandler<?> handler = new ModelEventHandler(modelClass, delegateClass, supplier);
if (!Util.isEmpty(filter, true)) if (!Util.isEmpty(filter, true))
handler.setFilter(filter); 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) { } catch (Exception e) {
if (s_log.isLoggable(Level.INFO)) if (s_log.isLoggable(Level.INFO))
s_log.log(Level.INFO, e.getMessage(), e); s_log.log(Level.INFO, e.getMessage(), e);
return null;
} }
} }

View File

@ -1076,4 +1076,15 @@ public class Core {
//fall back, should not reach here //fall back, should not reach here
return new DefaultTaxLookup(); return new DefaultTaxLookup();
} }
/**
* @return {@link DefaultAnnotationBasedEventManager}
*/
public static DefaultAnnotationBasedEventManager getDefaultAnnotationBasedEventManager() {
IServiceReferenceHolder<DefaultAnnotationBasedEventManager> serviceReference = Service.locator().locate(DefaultAnnotationBasedEventManager.class).getServiceReference();
if (serviceReference != null) {
return serviceReference.getService();
}
return null;
}
} }

View File

@ -26,7 +26,7 @@ package org.adempiere.base;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
@Component(immediate = true, service = {}) @Component(immediate = true, service = {DefaultAnnotationBasedEventManager.class})
public class DefaultAnnotationBasedEventManager extends AnnotationBasedEventManager { public class DefaultAnnotationBasedEventManager extends AnnotationBasedEventManager {
/** /**

View File

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

View File

@ -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<MTest> {
/**
* @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);
}
}