From dea19d29a07f25a5380d05ce3b888bc8ae3b7dba Mon Sep 17 00:00:00 2001 From: Boubaker Khanfir Date: Thu, 6 Jun 2024 12:46:31 +0100 Subject: [PATCH] feat: Simplify usage of ListenerService API - MEED-7009 - Meeds-io/meeds#2116 **Is your feature request related to a problem? Please describe.** Using current implementation of `ListenerService`, it's difficult to trigger and to add event listeners. In fact the source code is very noisy when handling `ListenerService`, by example - to add a Listener: ```java listenerService.addListener("event.to.catch", new EventListener<> { public void onEvent(Event event) { // Code .... } }); ``` - to trigger an event: ```java try { listenerService.broadcast("", s, d); } catch(Exception e) { .... } ``` This change will use `@FuntionalInterface` behavior to allow using Lambda expressions and avoid having to catch a checked exception while broadcasting event. --- .../services/listener/Listener.java | 10 +- .../services/listener/ListenerBase.java | 44 ++ .../services/listener/ListenerService.java | 445 ++++++++---------- 3 files changed, 234 insertions(+), 265 deletions(-) create mode 100644 exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/ListenerBase.java diff --git a/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/Listener.java b/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/Listener.java index e125867a5..7fb31ecf9 100644 --- a/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/Listener.java +++ b/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/Listener.java @@ -29,14 +29,6 @@ * @author Nhu Dinh Thuan * @LevelAPI Platform */ -public abstract class Listener extends BaseComponentPlugin -{ - - /** - * This method should be invoked when an event with the same name is - * broadcasted - * @param event the event instance - */ - public abstract void onEvent(Event event) throws Exception; +public abstract class Listener extends BaseComponentPlugin implements ListenerBase { } diff --git a/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/ListenerBase.java b/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/ListenerBase.java new file mode 100644 index 000000000..2a4ab83ee --- /dev/null +++ b/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/ListenerBase.java @@ -0,0 +1,44 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser 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. + */ +package org.exoplatform.services.listener; + +/** + * Created by The eXo Platform SAS
+ * This class is registered with the Listener service and is invoked when an + * event with the same name is broadcasted. You can have many listeners with the + * same name to listen to an event. + * + * @author Nhu Dinh Thuan + * @LevelAPI Platform + */ +@FunctionalInterface +public interface ListenerBase { + + /** + * This method should be invoked when an event with the same name is + * broadcasted + * + * @param event the event instance + */ + void onEvent(Event event) throws Exception; + + default String getName() { + return this.getClass().getName(); + } + +} diff --git a/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/ListenerService.java b/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/ListenerService.java index 1548e3ce3..fdca267d7 100644 --- a/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/ListenerService.java +++ b/exo.kernel.component.common/src/main/java/org/exoplatform/services/listener/ListenerService.java @@ -30,301 +30,234 @@ import org.exoplatform.services.log.Log; import org.exoplatform.services.naming.InitialContextInitializer; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by The eXo Platform SAS
- * - * Listener Service is reponsible for notifying the {@link Listener} - * when a given event is broadcasted. + * Listener Service is reponsible for notifying the {@link Listener} when a + * given event is broadcasted. * * @author : Nhu Dinh Thuan. * @LevelAPI Platform */ @DefinitionByType -public class ListenerService implements Startable -{ - /** - * This executor used for asynchronously event broadcast. - */ - private final ExecutorService executor; +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class ListenerService implements Startable { - /** - * Listeners by name map. - */ - private final Map> listeners_; + private static final Log LOG = ExoLogger.getLogger("exo.kernel.component.common.ListenerService"); - private static final Log LOG = ExoLogger.getLogger("exo.kernel.component.common.ListenerService"); + /** + * This executor used for asynchronously event broadcast. + */ + private final ExecutorService executor; - private final ExoContainer container; - - /** - * Construct a listener service. - */ - public ListenerService(ExoContainerContext ctx) - { - this(ctx, null, null); - } - - /** - * Construct a listener service. - */ - public ListenerService(ExoContainerContext ctx, InitialContextInitializer initializer) - { - this(ctx, initializer, null); - } - - /** - * Construct a listener service. - */ - public ListenerService(ExoContainerContext ctx, InitParams params) - { - this(ctx, null, params); - } + /** + * Listeners by name map. + */ + private final Map> listeners = new ConcurrentHashMap<>(); - /** - * Construct a listener service. - */ - public ListenerService(ExoContainerContext ctx, InitialContextInitializer initializer, InitParams params) - { - container = ctx.getContainer(); - listeners_ = new HashMap>(); - int poolSize = 1; + private final ExoContainer container; - if (params != null && params.getValueParam("asynchPoolSize") != null) - { - poolSize = Integer.parseInt(params.getValueParam("asynchPoolSize").getValue()); - } - executor = Executors.newFixedThreadPool(poolSize, new ListenerThreadFactory()); - } + /** + * Construct a listener service. + */ + public ListenerService(ExoContainerContext ctx) { + this(ctx, null, null); + } - /** - * This method is used to register a {@link Listener} to the events of the same - * name. It is similar to addListener(listener.getName(), listener) - * - * @param listener the listener to notify any time an even of the same name is - * triggered - */ - public void addListener(Listener listener) - { - addListener(listener.getName(), listener); - } + /** + * Construct a listener service. + */ + public ListenerService(ExoContainerContext ctx, InitialContextInitializer initializer) { + this(ctx, initializer, null); + } - /** - * This method is used to register a new {@link Listener}. Any time an - * event of the given event name has been triggered, the {@link Listener} will be - * notified. - * This method will: - *
    - *
  1. Check if it exists a list of listeners that have been registered for the - * given event name, create a new list if no list exists
  2. - *
  3. Add the listener to the list
  4. - *
- * @param eventName The name of the event to listen to - * @param listener The Listener to notify any time the event with the given - * name is triggered - */ - public void addListener(String eventName, Listener listener) - { - // Check is Listener or its superclass asynchronous, if so - wrap it in AsynchronousListener. - Class listenerClass = listener.getClass(); + /** + * Construct a listener service. + */ + public ListenerService(ExoContainerContext ctx, InitParams params) { + this(ctx, null, params); + } - do - { - if (listenerClass.isAnnotationPresent(Asynchronous.class)) - { - listener = new AsynchronousListener(listener); - break; - } - else - { - listenerClass = listenerClass.getSuperclass(); - } - } - while (listenerClass != null); + /** + * Construct a listener service. + */ + public ListenerService(ExoContainerContext ctx, + InitialContextInitializer initializer, // NOSONAR + InitParams params) { + container = ctx.getContainer(); + int poolSize = 1; - List list = listeners_.get(eventName); - if (list == null) - { - list = new ArrayList(); - listeners_.put(eventName, list); - } - list.add(listener); - } + if (params != null && params.getValueParam("asynchPoolSize") != null) { + poolSize = Integer.parseInt(params.getValueParam("asynchPoolSize").getValue()); + } + executor = Executors.newFixedThreadPool(poolSize, new ListenerThreadFactory()); + } - /** - * This method is used to broadcast an event. This method should: 1. Check if - * there is a list of listener that listen to the event name. 2. If there is a - * list of listener, create the event object with the given name , source and - * data 3. For each listener in the listener list, invoke the method - * onEvent(Event) - * - * @param The type of the source that broacast the event - * @param The type of the data that the source object is working on - * @param name The name of the event - * @param source The source object instance - * @param data The data object instance - * @throws Exception if an exception occurs - */ - public void broadcast(String name, S source, D data) throws Exception - { - List list = listeners_.get(name); - if (list == null) - return; - for (Listener listener : list) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("broadcasting event " + name + " on " + listener.getName()); - } + /** + * This method is used to register a {@link Listener} to the events of the + * same name. It is similar to addListener(listener.getName(), listener) + * + * @param listener the listener to notify any time an even of the same name is + * triggered + */ + public void addListener(Listener listener) { + addListener(listener.getName(), listener); + } - try - { - listener.onEvent(new Event(name, source, data)); - } - catch (Exception e) - { - LOG.error("Exception on broadcasting events occurs: " + e.getMessage(), e); - } + /** + * This method is used to register a new {@link Listener}. Any time an event + * of the given event name has been triggered, the {@link Listener} will be + * notified. This method will: + *
    + *
  1. Check if it exists a list of listeners that have been registered for + * the given event name, create a new list if no list exists
  2. + *
  3. Add the listener to the list
  4. + *
+ * + * @param eventName The name of the event to listen to + * @param listener The Listener to notify any time the event with the given + * name is triggered + */ + public void addListener(String eventName, ListenerBase listener) { + if (LOG.isDebugEnabled()) { + LOG.debug("Adding listener {} on event {}", listener.getName(), eventName); + } + // Check is Listener or its superclass asynchronous, if so - wrap it in + // AsynchronousListener. + Class listenerClass = listener.getClass(); + do { + if (listenerClass.isAnnotationPresent(Asynchronous.class)) { + listener = new AsynchronousListener(listener); + break; + } else { + listenerClass = listenerClass.getSuperclass(); } - } + } while (listenerClass != null); + listeners.computeIfAbsent(eventName, k -> new Vector<>()) + .add(listener); + } - /** - * This method is used when a developer want to implement his own event object - * and broadcast the event. The method should: 1. Check if there is a list of - * listener that listen to the event name. 2. If there is a list of the - * listener, ror each listener in the listener list, invoke the method - * onEvent(Event) - * - * @param The type of the event object, the type of the event object has - * to be extended from the Event type - * @param event The event instance - * @throws Exception - */ - public void broadcast(T event) throws Exception - { - List list = listeners_.get(event.getEventName()); - if (list == null) - { - return; + /** + * This method is used to broadcast an event. This method should: 1. Check if + * there is a list of listener that listen to the event name. 2. If there is a + * list of listener, create the event object with the given name , source and + * data 3. For each listener in the listener list, invoke the method + * onEvent(Event) + * + * @param The type of the source that broacast the event + * @param The type of the data that the source object is working on + * @param name The name of the event + * @param source The source object instance + * @param data The data object instance + */ + public void broadcast(String name, S source, D data) { + List list = listeners.get(name); + if (list == null) + return; + for (ListenerBase listener : list) { + if (LOG.isDebugEnabled()) { + LOG.debug("broadcasting event " + name + " on " + listener.getName()); } - for (Listener listener : list) - { - try - { - listener.onEvent(event); - } - catch (Exception e) - { - LOG.error("Exception on broadcasting events occurs: " + e.getMessage(), e); - } - } - } - - /** - * This AsynchronousListener is a wrapper for original listener, that - * executes wrapped listeners onEvent() in separate thread. - */ - protected class AsynchronousListener extends Listener - { - private Listener listener; - - public AsynchronousListener(Listener listener) - { - this.listener = listener; + try { + listener.onEvent(new Event<>(name, source, data)); + } catch (Exception e) { + LOG.warn("Exception on broadcasting events occurred while broadcasting event {}. Continue braodcasting events.", + name, + e); } + } + } - @Override - public String getName() - { - return listener.getName(); + /** + * This method is used when a developer want to implement his own event object + * and broadcast the event. The method should: 1. Check if there is a list of + * listener that listen to the event name. 2. If there is a list of the + * listener, ror each listener in the listener list, invoke the method + * onEvent(Event) + * + * @param The type of the event object, the type of the event object has + * to be extended from the Event type + * @param event The event instance + */ + public void broadcast(T event) { + List list = listeners.get(event.getEventName()); + if (list == null) { + return; + } + for (ListenerBase listener : list) { + try { + listener.onEvent(event); + } catch (Exception e) { + LOG.warn("Exception on broadcasting events occurred while broadcasting event {}. Continue braodcasting events.", + event.getEventName(), + e); } + } + } - @Override - public void setName(String s) - { - listener.setName(s); - } + /** + * This AsynchronousListener is a wrapper for original listener, that executes + * wrapped listeners onEvent() in separate thread. + */ + protected class AsynchronousListener implements ListenerBase { + private ListenerBase listener; - @Override - public String getDescription() - { - return listener.getDescription(); - } + public AsynchronousListener(ListenerBase listener) { + this.listener = listener; + } - @Override - public void setDescription(String s) - { - listener.setDescription(s); - } + @Override + public void onEvent(Event event) { + executor.execute(new RunListener(listener, event)); + } + } - @Override - public void onEvent(Event event) throws Exception - { - executor.execute(new RunListener(listener, event)); - } - } + /** + * This thread executes listener.onEvent(event) method. + */ + protected class RunListener implements Runnable { - /** - * This thread executes listener.onEvent(event) method. - */ - protected class RunListener implements Runnable - { - private Listener listener; + private ListenerBase listener; - private Event event; - - private final ThreadContextHandler handler; + private Event event; - public RunListener(Listener listener, Event event) - { - this.listener = listener; - this.event = event; - this.handler = new ThreadContextHandler(container); - handler.store(); - } + private final ThreadContextHandler handler; - /** - * {@inheritDoc} - */ - public void run() - { - try - { - ExoContainerContext.setCurrentContainer(container); + public RunListener(ListenerBase listener, Event event) { + this.listener = listener; + this.event = event; + this.handler = new ThreadContextHandler(container); + handler.store(); + } - RequestLifeCycle.begin(container); - handler.push(); - listener.onEvent(event); - } - catch (Exception e) - { - // Do not throw exception. Event is asynchronous so just report error. - // Must say that exception will be ignored even in synchronous events. - LOG.error("Exception on broadcasting events occurs: " + e.getMessage(), e); - } - finally - { - try - { - handler.restore(); - RequestLifeCycle.end(); - } - finally - { - ExoContainerContext.setCurrentContainer(null); - } - } + /** + * {@inheritDoc} + */ + public void run() { + ExoContainerContext.setCurrentContainer(container); + RequestLifeCycle.begin(container); + try { + handler.push(); + listener.onEvent(event); + } catch (Exception e) { + // Do not throw exception. Event is asynchronous so just report error. + // Must say that exception will be ignored even in synchronous events. + LOG.error("Exception on broadcasting events occurs: " + e.getMessage(), e); + } finally { + try { + handler.restore(); + RequestLifeCycle.end(); + } finally { + ExoContainerContext.setCurrentContainer(null); + } } - } - - @Override - public void start() { + } } @Override