From 28638d075bde4f1219e1b507c4dd02ff7da08ecd Mon Sep 17 00:00:00 2001 From: hengsin Date: Thu, 28 Oct 2021 17:21:08 +0800 Subject: [PATCH] IDEMPIERE-5019 add org.adempiere.base.annotation.EventTopicDelegate annotation (#952) --- .../addressvalidationeventhandler.xml | 5 - ...ase.DefaultAnnotationBasedEventManager.xml | 4 + .../base/AnnotationBasedEventManager.java | 372 ++++++++++++++++++ .../DefaultAnnotationBasedEventManager.java | 43 ++ .../base/annotation/EventTopicDelegate.java | 50 +++ .../base/annotation/ImportEventTopic.java | 49 +++ .../base/annotation/ModelEventTopic.java | 50 +++ .../base/annotation/ProcessEventTopic.java | 49 +++ .../event/AddressValidationEventHandler.java | 78 ---- .../base/event/annotations/EventDelegate.java | 1 + .../AddressValidationEventDelegate.java | 98 +++++ .../test/event/EventHandlerTest.java | 52 +++ 12 files changed, 768 insertions(+), 83 deletions(-) delete mode 100644 org.adempiere.base/OSGI-INF/addressvalidationeventhandler.xml create mode 100644 org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedEventManager.xml create mode 100644 org.adempiere.base/src/org/adempiere/base/AnnotationBasedEventManager.java create mode 100644 org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedEventManager.java create mode 100644 org.adempiere.base/src/org/adempiere/base/annotation/EventTopicDelegate.java create mode 100644 org.adempiere.base/src/org/adempiere/base/annotation/ImportEventTopic.java create mode 100644 org.adempiere.base/src/org/adempiere/base/annotation/ModelEventTopic.java create mode 100644 org.adempiere.base/src/org/adempiere/base/annotation/ProcessEventTopic.java delete mode 100644 org.adempiere.base/src/org/adempiere/base/event/AddressValidationEventHandler.java create mode 100644 org.adempiere.base/src/org/adempiere/base/event/delegate/AddressValidationEventDelegate.java diff --git a/org.adempiere.base/OSGI-INF/addressvalidationeventhandler.xml b/org.adempiere.base/OSGI-INF/addressvalidationeventhandler.xml deleted file mode 100644 index cb394594fe..0000000000 --- a/org.adempiere.base/OSGI-INF/addressvalidationeventhandler.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedEventManager.xml b/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedEventManager.xml new file mode 100644 index 0000000000..e5b18b745e --- /dev/null +++ b/org.adempiere.base/OSGI-INF/org.adempiere.base.DefaultAnnotationBasedEventManager.xml @@ -0,0 +1,4 @@ + + + + \ 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 new file mode 100644 index 0000000000..82f25b39b0 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/AnnotationBasedEventManager.java @@ -0,0 +1,372 @@ +/*********************************************************************** + * 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.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.logging.Level; + +import org.adempiere.base.annotation.EventTopicDelegate; +import org.adempiere.base.annotation.ImportEventTopic; +import org.adempiere.base.annotation.ModelEventTopic; +import org.adempiere.base.annotation.ProcessEventTopic; +import org.adempiere.base.event.IEventManager; +import org.adempiere.base.event.annotations.EventDelegate; +import org.adempiere.base.event.annotations.ModelEventDelegate; +import org.adempiere.base.event.annotations.ModelEventHandler; +import org.adempiere.base.event.annotations.SimpleEventHandler; +import org.adempiere.base.event.annotations.imp.ImportEventDelegate; +import org.adempiere.base.event.annotations.imp.ImportEventHandler; +import org.adempiere.base.event.annotations.process.ProcessEventDelegate; +import org.adempiere.base.event.annotations.process.ProcessEventHandler; +import org.compiere.model.PO; +import org.compiere.util.CLogger; +import org.compiere.util.Util; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventHandler; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +import io.github.classgraph.AnnotationClassRef; +import io.github.classgraph.AnnotationInfo; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ScanResult; + +/** + * Scan, discover and register classes with {@link EventTopicDelegate} annotation + * @author hengsin + * + */ +public abstract class AnnotationBasedEventManager { + + private static final CLogger s_log = CLogger.getCLogger(AnnotationBasedEventManager.class); + + private IEventManager eventManager; + private BundleContext bundleContext; + private List handlers = new ArrayList<>(); + + private ServiceTracker serviceTracker; + + /** + * default constructor + */ + public AnnotationBasedEventManager() { + } + + /** + * Subclass would override this to define the list of packages to perform the scan, discover and register operation + * @return packages to scan + */ + public abstract String[] getPackages(); + + @Activate + public void activate(ComponentContext context) { + bundleContext = context.getBundleContext(); + serviceTracker = new ServiceTracker(bundleContext, IEventManager.class, + new Customizer()); + serviceTracker.open(); + } + + @Deactivate + public void deactivate(ComponentContext context) { + if (serviceTracker != null) { + serviceTracker.close(); + serviceTracker = null; + } + if (eventManager != null) { + if (handlers.size() > 0) + unbindService(eventManager); + eventManager = null; + } + } + + /** + * + * @param eventManager + */ + protected void bindService(IEventManager eventManager) { + this.eventManager = eventManager; + if (eventManager != null) + scan(); + } + + /** + * + * @param eventManager + */ + protected void unbindService(IEventManager eventManager) { + if (eventManager != null && eventManager == this.eventManager) { + if (handlers.size() > 0) { + for(EventHandler handler : handlers) + eventManager.unregister(handler); + } + this.eventManager = null; + } + } + + /** + * Perform scan, discover and register of annotated classes + */ + protected void scan() { + long start = System.currentTimeMillis(); + ClassLoader classLoader = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader(); + + ClassGraph graph = new ClassGraph() + .enableAnnotationInfo() + .overrideClassLoaders(classLoader) + .disableNestedJarScanning() + .disableModuleScanning() + .acceptPackagesNonRecursive(getPackages()); + + try (ScanResult scanResult = graph.scan()) + { + for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(EventTopicDelegate.class)) { + if (classInfo.isAbstract()) + continue; + 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); + } else if (classInfo.hasAnnotation(ImportEventTopic.class)) { + AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(ImportEventTopic.class); + importEventDelegate(classLoader, className, annotationInfo, filter); + } else if (classInfo.hasAnnotation(ProcessEventTopic.class)) { + AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(ProcessEventTopic.class); + processEventDelegate(classLoader, className, annotationInfo, filter); + } else { + simpleEventDelegate(classLoader, className, filter); + } + } + } + long end = System.currentTimeMillis(); + if (s_log.isLoggable(Level.INFO)) + s_log.info(this.getClass().getSimpleName() + " loaded "+handlers.size() +" classes in " + +((end-start)/1000f) + "s"); + + } + + private void simpleEventDelegate(ClassLoader classLoader, String className, String filter) { + try { + @SuppressWarnings("unchecked") + Class delegateClass = (Class) classLoader.loadClass(className); + Constructor constructor = delegateClass.getConstructor(Event.class); + EventDelegateSupplier supplier = new EventDelegateSupplier(constructor); + SimpleEventHandler handler = new SimpleEventHandler(delegateClass, supplier); + if (!Util.isEmpty(filter, true)) + handler.setFilter(filter); + handlers.add(handler); + eventManager.register(handler.getTopics(), handler.getFilter(), handler); + } catch (Exception e) { + if (s_log.isLoggable(Level.INFO)) + s_log.log(Level.INFO, e.getMessage(), e); + } + } + + private void processEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) { + try { + String processUUID = (String) annotationInfo.getParameterValues().getValue("processUUID"); + @SuppressWarnings("unchecked") + Class delegateClass = (Class) classLoader.loadClass(className); + Constructor constructor = delegateClass.getConstructor(Event.class); + ProcessDelegateSupplier supplier = new ProcessDelegateSupplier(constructor); + 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); + } catch (Exception e) { + if (s_log.isLoggable(Level.INFO)) + s_log.log(Level.INFO, e.getMessage(), e); + } + } + + private void importEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) { + try { + String importTableName = (String) annotationInfo.getParameterValues().getValue("importTableName"); + @SuppressWarnings("unchecked") + Class delegateClass = (Class) classLoader.loadClass(className); + Constructor constructor = delegateClass.getConstructor(Event.class); + ImportDelegateSupplier supplier = new ImportDelegateSupplier(constructor); + 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); + } catch (Exception e) { + if (s_log.isLoggable(Level.INFO)) + s_log.log(Level.INFO, e.getMessage(), e); + } + } + + private void modelEventDelegate(ClassLoader classLoader, String className, AnnotationInfo annotationInfo, String filter) { + try { + AnnotationClassRef classRef = (AnnotationClassRef) annotationInfo.getParameterValues().getValue("modelClass"); + @SuppressWarnings("unchecked") + Class modelClass = (Class)classRef.loadClass(); + @SuppressWarnings("unchecked") + Class> delegateClass = (Class>) classLoader.loadClass(className); + Constructor constructor = delegateClass.getDeclaredConstructor(modelClass, Event.class); + ModelDelegateSupplier supplier = new ModelDelegateSupplier(constructor); + @SuppressWarnings({ "rawtypes", "unchecked" }) + 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); + } catch (Exception e) { + if (s_log.isLoggable(Level.INFO)) + s_log.log(Level.INFO, e.getMessage(), e); + } + } + + private class Customizer implements ServiceTrackerCustomizer { + @Override + public IEventManager addingService(ServiceReference reference) { + IEventManager eventManager = bundleContext.getService(reference); + bindService(eventManager); + return eventManager; + } + + @Override + public void modifiedService(ServiceReference reference, IEventManager service) { + if (eventManager != null && eventManager != service) { + unbindService(eventManager); + bindService(service); + } + } + + @Override + public void removedService(ServiceReference reference, IEventManager service) { + unbindService(service); + } + } + + private static class ModelDelegateSupplier implements BiFunction> { + + private Constructor constructor; + + private ModelDelegateSupplier(Constructor constructor) { + this.constructor = constructor; + } + + @Override + public ModelEventDelegate apply(PO t, Event u) { + ModelEventDelegate delegate = null; + if (constructor != null) { + try { + delegate = (ModelEventDelegate) constructor.newInstance(t, u); + } catch (Exception e) { + constructor = null; + s_log.log(Level.WARNING, e.getMessage(), e); + } + } + return delegate; + } + } + + private static class ImportDelegateSupplier implements Function { + + private Constructor constructor; + + private ImportDelegateSupplier(Constructor constructor) { + this.constructor = constructor; + } + + @Override + public ImportEventDelegate apply(Event t) { + ImportEventDelegate delegate = null; + if (constructor != null) { + try { + delegate = (ImportEventDelegate) constructor.newInstance(t); + } catch (Exception e) { + constructor = null; + s_log.log(Level.WARNING, e.getMessage(), e); + } + } + return delegate; + } + + } + + private static class ProcessDelegateSupplier implements Function { + + private Constructor constructor; + + private ProcessDelegateSupplier(Constructor constructor) { + this.constructor = constructor; + } + + @Override + public ProcessEventDelegate apply(Event t) { + ProcessEventDelegate delegate = null; + if (constructor != null) { + try { + delegate = (ProcessEventDelegate) constructor.newInstance(t); + } catch (Exception e) { + constructor = null; + s_log.log(Level.WARNING, e.getMessage(), e); + } + } + return delegate; + } + + } + + private static class EventDelegateSupplier implements Function { + + private Constructor constructor; + + private EventDelegateSupplier(Constructor constructor) { + this.constructor = constructor; + } + + @Override + public EventDelegate apply(Event t) { + EventDelegate delegate = null; + if (constructor != null) { + try { + delegate = (EventDelegate) constructor.newInstance(t); + } catch (Exception e) { + constructor = null; + s_log.log(Level.WARNING, e.getMessage(), e); + } + } + return delegate; + } + + } +} diff --git a/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedEventManager.java b/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedEventManager.java new file mode 100644 index 0000000000..369a75956e --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/DefaultAnnotationBasedEventManager.java @@ -0,0 +1,43 @@ +/*********************************************************************** + * 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 org.osgi.service.component.annotations.Component; + +@Component(immediate = true, service = {}) +public class DefaultAnnotationBasedEventManager extends AnnotationBasedEventManager { + + /** + * default constructor + */ + public DefaultAnnotationBasedEventManager() { + } + + @Override + public String[] getPackages() { + return new String[] {"org.adempiere.base.event.delegate"}; + } + +} diff --git a/org.adempiere.base/src/org/adempiere/base/annotation/EventTopicDelegate.java b/org.adempiere.base/src/org/adempiere/base/annotation/EventTopicDelegate.java new file mode 100644 index 0000000000..667a0010be --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/annotation/EventTopicDelegate.java @@ -0,0 +1,50 @@ +/*********************************************************************** + * 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.annotation; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.adempiere.base.event.annotations.EventDelegate; + +/** + * + * Annotation for OSGi Event Topic Delegate. + * Works with {@link EventDelegate} and its sub classes + * @author hengsin + * + */ +@Retention(RUNTIME) +@Target(ElementType.TYPE) +public @interface EventTopicDelegate { + /** + * Optional event topic filter + * @return filter + */ + String filter() default ""; +} diff --git a/org.adempiere.base/src/org/adempiere/base/annotation/ImportEventTopic.java b/org.adempiere.base/src/org/adempiere/base/annotation/ImportEventTopic.java new file mode 100644 index 0000000000..8df3d89ee1 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/annotation/ImportEventTopic.java @@ -0,0 +1,49 @@ +/*********************************************************************** + * 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.annotation; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.adempiere.base.event.annotations.imp.ImportEventDelegate; + +/** + * Define parameter for {@link ImportEventDelegate} + * Works with classes with {@link EventTopicDelegate} annotation + * @author hengsin + * + */ +@Retention(RUNTIME) +@Target(ElementType.TYPE) +public @interface ImportEventTopic { + /** + * Import table (I_*) name for {@link ImportEventDelegate} + * @return import table name + */ + String importTableName(); +} diff --git a/org.adempiere.base/src/org/adempiere/base/annotation/ModelEventTopic.java b/org.adempiere.base/src/org/adempiere/base/annotation/ModelEventTopic.java new file mode 100644 index 0000000000..2c0cdb4176 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/annotation/ModelEventTopic.java @@ -0,0 +1,50 @@ +/*********************************************************************** + * 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.annotation; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.adempiere.base.event.annotations.ModelEventDelegate; +import org.compiere.model.PO; + +/** + * Specify parameter for {@link ModelEventDelegate} + * Works with classes with {@link EventTopicDelegate} annotation + * @author hengsin + * + */ +@Retention(RUNTIME) +@Target(ElementType.TYPE) +public @interface ModelEventTopic { + /** + * Model class (M* or X_*) for {@link ModelEventDelegate} + * @return model class + */ + Class modelClass(); +} diff --git a/org.adempiere.base/src/org/adempiere/base/annotation/ProcessEventTopic.java b/org.adempiere.base/src/org/adempiere/base/annotation/ProcessEventTopic.java new file mode 100644 index 0000000000..aeac806a63 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/annotation/ProcessEventTopic.java @@ -0,0 +1,49 @@ +/*********************************************************************** + * 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.annotation; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.adempiere.base.event.annotations.process.ProcessEventDelegate; + +/** + * Define parameter for {@link ProcessEventDelegate} + * Works with classes with {@link EventTopicDelegate} annotation + * @author hengsin + * + */ +@Retention(RUNTIME) +@Target(ElementType.TYPE) +public @interface ProcessEventTopic { + /** + * AD_Process.AD_Process_UU (uuid) value for {@link ProcessEventDelegate} + * @return process uuid + */ + String processUUID(); +} diff --git a/org.adempiere.base/src/org/adempiere/base/event/AddressValidationEventHandler.java b/org.adempiere.base/src/org/adempiere/base/event/AddressValidationEventHandler.java deleted file mode 100644 index 7b8fb7da16..0000000000 --- a/org.adempiere.base/src/org/adempiere/base/event/AddressValidationEventHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2013 Elaine Tan * - * Copyright (C) 2013 Trek Global - * This program is free software; you can redistribute it and/or modify it * - * under the terms version 2 of the GNU General Public License as published * - * by the Free Software Foundation. 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., * - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * - *****************************************************************************/ -package org.adempiere.base.event; - -import java.util.StringTokenizer; - -import org.compiere.model.I_C_Location; -import org.compiere.model.MAddressValidation; -import org.compiere.model.MLocation; -import org.compiere.model.MSysConfig; -import org.compiere.model.PO; -import org.osgi.service.event.Event; - -/** - * Address validation event handler - * @author Elaine - * - */ -public class AddressValidationEventHandler extends AbstractEventHandler { - - @Override - protected void doHandleEvent(Event event) { - String topic = event.getTopic(); - if (topic.equals(IEventTopics.PO_BEFORE_NEW) || topic.equals(IEventTopics.PO_BEFORE_CHANGE)) - { - PO po = getPO(event); - if (po.get_TableName().equals(I_C_Location.Table_Name)) - { - MLocation location = (MLocation) po; - - String addressValidation = MSysConfig.getValue(MSysConfig.ADDRESS_VALIDATION, null, location.getAD_Client_ID()); - boolean isEnabled = false; - if (addressValidation != null && addressValidation.trim().length() > 0 && location.getCountry() != null) - { - StringTokenizer st = new StringTokenizer(addressValidation, ";"); - while (st.hasMoreTokens()) - { - String token = st.nextToken().trim(); - if (token.equals(location.getCountry().getCountryCode().trim())) - { - isEnabled = true; - break; - } - } - } - - if (!isEnabled) - return; - - MAddressValidation validation = null; - if (location.getC_AddressValidation_ID() > 0) - validation = new MAddressValidation(location.getCtx(), location.getC_AddressValidation_ID(), location.get_TrxName()); - if (validation == null) - validation = MAddressValidation.getDefaultAddressValidation(location.getCtx(), location.getAD_Client_ID(), location.get_TrxName()); - if (validation != null) - location.processOnline(validation.getC_AddressValidation_ID()); - } - } - } - - @Override - protected void initialize() { - registerTableEvent(IEventTopics.PO_BEFORE_NEW, I_C_Location.Table_Name); - registerTableEvent(IEventTopics.PO_BEFORE_CHANGE, I_C_Location.Table_Name); - } - -} \ No newline at end of file diff --git a/org.adempiere.base/src/org/adempiere/base/event/annotations/EventDelegate.java b/org.adempiere.base/src/org/adempiere/base/event/annotations/EventDelegate.java index f0b515d673..3151bbb21a 100644 --- a/org.adempiere.base/src/org/adempiere/base/event/annotations/EventDelegate.java +++ b/org.adempiere.base/src/org/adempiere/base/event/annotations/EventDelegate.java @@ -30,6 +30,7 @@ import org.osgi.service.event.Event; * * Annotation driven event delegate base class that works together with {@link BaseEventHandler}. * Subclass implementation doesn't have to be thread safe as event delegate is create and throw away for each event call. + * Subclass should use {@link EventTopic} or one of its derived annotation to define the event topic to handle * @author hengsin * */ diff --git a/org.adempiere.base/src/org/adempiere/base/event/delegate/AddressValidationEventDelegate.java b/org.adempiere.base/src/org/adempiere/base/event/delegate/AddressValidationEventDelegate.java new file mode 100644 index 0000000000..90d29e24c8 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/event/delegate/AddressValidationEventDelegate.java @@ -0,0 +1,98 @@ +/*********************************************************************** + * 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.event.delegate; + +import java.util.StringTokenizer; +import java.util.concurrent.atomic.AtomicInteger; + +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.MAddressValidation; +import org.compiere.model.MLocation; +import org.compiere.model.MSysConfig; +import org.osgi.service.event.Event; + +/** + * + * @author hengsin + * + */ +@EventTopicDelegate +@ModelEventTopic(modelClass = MLocation.class) +public class AddressValidationEventDelegate extends ModelEventDelegate { + + /** + * + * @param po + * @param event + */ + public AddressValidationEventDelegate(MLocation po, Event event) { + super(po, event); + } + + @BeforeNew + @BeforeChange + public void beforeCreateOrUpdate() { + MLocation location = getModel(); + + //for unit test checking (see org.idempiere.test.event.EventHandlerTest#testAddressValidationDelegate) + if (getEvent().containsProperty(getClass().getName())) { + Object value = getEvent().getProperty(getClass().getName()); + if (value instanceof AtomicInteger) { + ((AtomicInteger) value).set(1); + } + } + + String addressValidation = MSysConfig.getValue(MSysConfig.ADDRESS_VALIDATION, null, location.getAD_Client_ID()); + boolean isEnabled = false; + if (addressValidation != null && addressValidation.trim().length() > 0 && location.getCountry() != null) + { + StringTokenizer st = new StringTokenizer(addressValidation, ";"); + while (st.hasMoreTokens()) + { + String token = st.nextToken().trim(); + if (token.equals(location.getCountry().getCountryCode().trim())) + { + isEnabled = true; + break; + } + } + } + + if (!isEnabled) + return; + + MAddressValidation validation = null; + if (location.getC_AddressValidation_ID() > 0) + validation = new MAddressValidation(location.getCtx(), location.getC_AddressValidation_ID(), location.get_TrxName()); + if (validation == null) + validation = MAddressValidation.getDefaultAddressValidation(location.getCtx(), location.getAD_Client_ID(), location.get_TrxName()); + if (validation != null) + location.processOnline(validation.getC_AddressValidation_ID()); + } +} diff --git a/org.idempiere.test/src/org/idempiere/test/event/EventHandlerTest.java b/org.idempiere.test/src/org/idempiere/test/event/EventHandlerTest.java index e8a337b3f6..c3723a86a3 100644 --- a/org.idempiere.test/src/org/idempiere/test/event/EventHandlerTest.java +++ b/org.idempiere.test/src/org/idempiere/test/event/EventHandlerTest.java @@ -31,8 +31,11 @@ import static org.junit.jupiter.api.Assertions.fail; import java.math.BigDecimal; import java.sql.Timestamp; +import java.util.concurrent.atomic.AtomicInteger; import org.adempiere.base.Core; +import org.adempiere.base.event.EventManager; +import org.adempiere.base.event.EventProperty; import org.adempiere.base.event.FactsEventData; import org.adempiere.base.event.annotations.AfterLogin; import org.adempiere.base.event.annotations.EventDelegate; @@ -52,12 +55,16 @@ import org.adempiere.model.ImportValidator; import org.compiere.model.MBPartner; import org.compiere.model.MInOut; import org.compiere.model.MInOutLine; +import org.compiere.model.MLocation; import org.compiere.model.MOrder; import org.compiere.model.MOrderLine; import org.compiere.model.MOrg; import org.compiere.model.MProcess; import org.compiere.model.MProduct; +import org.compiere.model.MSysConfig; import org.compiere.model.ModelValidationEngine; +import org.compiere.model.ModelValidator; +import org.compiere.model.PO; import org.compiere.model.X_I_BPartner; import org.compiere.model.X_I_Product; import org.compiere.process.DocAction; @@ -66,6 +73,7 @@ import org.compiere.process.ImportBPartner; import org.compiere.process.ImportProduct; import org.compiere.process.ProcessInfo; import org.compiere.process.ServerProcessCtl; +import org.compiere.util.CacheMgt; import org.compiere.util.Env; import org.compiere.util.KeyNamePair; import org.compiere.util.Login; @@ -315,6 +323,50 @@ public class EventHandlerTest extends AbstractTestCase { "MyAfterImportDelegate not call. context="+Env.getContext(Env.getCtx(), MyAfterImportDelegate.class.getName())); } + @Test + @Order(8) + public void testAddressValidationDelegate() { + int addressValidationSysConfigId = 200033; + String delegateName = "org.adempiere.base.event.delegate.AddressValidationEventDelegate"; + MSysConfig sysconfig = new MSysConfig(Env.getCtx(), addressValidationSysConfigId, null); + String currentValue = sysconfig.getValue(); + try { + try { + PO.setCrossTenantSafe(); + sysconfig.setValue("US"); + sysconfig.saveEx(); + } finally { + PO.clearCrossTenantSafe(); + } + + CacheMgt.get().reset(); + + MLocation location = new MLocation(Env.getCtx(), 0, getTrxName()); + location.setC_Country_ID(100); + AtomicInteger count = new AtomicInteger(0); + Event event = EventManager.newEvent(ModelValidator.tableEventTopics[ModelValidator.TYPE_BEFORE_NEW], + new EventProperty(EventManager.EVENT_DATA, location), new EventProperty(EventManager.TABLE_NAME_PROPERTY, location.get_TableName()), + new EventProperty(delegateName, count)); + EventManager.getInstance().sendEvent(event); + assertTrue(count.get()==1, "AddressValidationEventDelegate not call for MLocation Before New Event"); + + count = new AtomicInteger(0); + event = EventManager.newEvent(ModelValidator.tableEventTopics[ModelValidator.TYPE_BEFORE_CHANGE], + new EventProperty(EventManager.EVENT_DATA, location), new EventProperty(EventManager.TABLE_NAME_PROPERTY, location.get_TableName()), + new EventProperty(delegateName, count)); + EventManager.getInstance().sendEvent(event); + assertTrue(count.get()==1, "AddressValidationEventDelegate not call for MLocation Before Change Event"); + } finally { + try { + PO.setCrossTenantSafe(); + sysconfig.setValue(currentValue); + sysconfig.saveEx(); + } finally { + PO.clearCrossTenantSafe(); + } + } + } + private final static class MyBPBeforeNewDelegate extends ModelEventDelegate { public MyBPBeforeNewDelegate(MBPartner bp, Event event) {