View Javadoc
1   /*
2    * *************************************************************************************************************************************************************
3    *
4    * TheseFoolishThings: Miscellaneous utilities
5    * http://tidalwave.it/projects/thesefoolishthings
6    *
7    * Copyright (C) 2009 - 2026 by Tidalwave s.a.s. (http://tidalwave.it)
8    *
9    * *************************************************************************************************************************************************************
10   *
11   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12   * You may obtain a copy of the License at
13   *
14   *     http://www.apache.org/licenses/LICENSE-2.0
15   *
16   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17   * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
18   *
19   * *************************************************************************************************************************************************************
20   *
21   * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
22   * git clone https://github.com/tidalwave-it/thesefoolishthings-src
23   *
24   * *************************************************************************************************************************************************************
25   */
26  package it.tidalwave.messagebus.spi;
27  
28  import java.lang.ref.WeakReference;
29  // import javax.annotation.concurrent.ThreadSafe;
30  import jakarta.annotation.Nonnull;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.concurrent.CopyOnWriteArrayList;
35  import java.util.concurrent.Executor;
36  import java.util.concurrent.Executors;
37  import it.tidalwave.messagebus.MessageBus;
38  import lombok.Getter;
39  import lombok.extern.slf4j.Slf4j;
40  
41  /***************************************************************************************************************************************************************
42   * An implementation of {@link MessageBus}. Messages are delivered by means of an {@link Executor}, according to a {@link MessageDelivery} strategy. A listener
43   * subscribed to a topic receives the messages of that class and of its subclasses (or implementations, if the topic is an interface); if a listener throws an
44   * exception, it is logged and the message is delivered to the other listeners anyway. This class is thread-safe.
45   * @author  Fabrizio Giudici
46   **************************************************************************************************************************************************************/
47  /* @ThreadSafe */  @Slf4j @SuppressWarnings("this-escape")
48  public class SimpleMessageBus implements MessageBus
49    {
50      private final Map<Class<?>, List<WeakReference<Listener<?>>>> listenersMapByTopic = new ConcurrentHashMap<>();
51  
52      private final MessageDelivery messageDelivery;
53      
54      /***********************************************************************************************************************************************************
55       * The {@link Executor} used to deliver messages.
56       * @return            the {@code Executor}
57       **********************************************************************************************************************************************************/
58      @Getter
59      private final Executor executor;
60      
61      /***********************************************************************************************************************************************************
62       * Creates a new instance with a {@link SimpleAsyncMessageDelivery} strategy for delivery. It will use its own pool of 10 threads.
63       **********************************************************************************************************************************************************/
64      public SimpleMessageBus() 
65        {
66          this(Executors.newFixedThreadPool(10));
67        }
68  
69      /***********************************************************************************************************************************************************
70       * Creates a new instance given an executor and a {@link SimpleAsyncMessageDelivery} strategy for delivery.
71       * @param   executor          the {@link Executor}
72       **********************************************************************************************************************************************************/
73      public SimpleMessageBus (@Nonnull final Executor executor)
74        {
75          this(executor, new SimpleAsyncMessageDelivery());
76        }
77      
78      /***********************************************************************************************************************************************************
79       * Creates a new instance given an executor and a strategy for delivery.
80       * @param   executor          the {@link Executor}
81       * @param   messageDelivery   the strategy for delivery
82       **********************************************************************************************************************************************************/
83      public SimpleMessageBus (@Nonnull final Executor executor, @Nonnull final MessageDelivery messageDelivery)
84        {
85          this.executor = executor;
86          this.messageDelivery = messageDelivery;
87          this.messageDelivery.initialize(this);
88          log.info("MessageBusSupport configured with {}", messageDelivery);
89        }
90       
91      /***********************************************************************************************************************************************************
92       * {@inheritDoc}
93       **********************************************************************************************************************************************************/
94      @Override
95      public <T> void publish (@Nonnull final T message)
96        {
97          publish((Class<T>)message.getClass(), message);
98        }
99  
100     /***********************************************************************************************************************************************************
101      * {@inheritDoc}
102      **********************************************************************************************************************************************************/
103     @Override
104     public <T> void publish (@Nonnull final Class<T> topic, @Nonnull final T message)
105       {
106         log.trace("publish({}, {})", topic, message);
107         messageDelivery.deliverMessage(topic, message);
108       }
109 
110     /***********************************************************************************************************************************************************
111      * {@inheritDoc}
112      **********************************************************************************************************************************************************/
113     @Override
114     public <T> void subscribe (@Nonnull final Class<T> topic, @Nonnull final Listener<T> listener)
115       {
116         log.debug("subscribe({}, {})", topic, listener);
117         findListenersByTopic(topic).add(new WeakReference<>(listener));
118       }
119 
120     /***********************************************************************************************************************************************************
121      * {@inheritDoc}
122      **********************************************************************************************************************************************************/
123     @Override
124     public void unsubscribe (@Nonnull final Listener<?> listener)
125       {
126         log.debug("unsubscribe({})", listener);
127 
128         for (final var list : listenersMapByTopic.values())
129           {
130             list.removeIf(ref -> (ref.get() == null) || (ref.get() == listener));
131           }
132       }
133 
134     /***********************************************************************************************************************************************************
135      * Dispatches a message to all the listeners subscribed to its topic, or to a supertype of it. It is called by the {@link MessageDelivery} strategy.
136      * @param   <T>       the static type of the topic
137      * @param   topic     the dynamic type of the topic
138      * @param   message   the message
139      **********************************************************************************************************************************************************/
140     protected <T> void dispatchMessage (@Nonnull final Class<T> topic, @Nonnull final T message)
141       {
142         for (final var e : listenersMapByTopic.entrySet())
143           {
144             if (e.getKey().isAssignableFrom(topic))
145               {
146                 final List<WeakReference<MessageBus.Listener<T>>> listeners = (List)e.getValue();
147 
148                 for (final var listenerReference : listeners)
149                   {
150                     final var listener = listenerReference.get();
151 
152                     if (listener != null)
153                       {
154                         try
155                           {
156                             listener.notify(message);
157                           }
158                         catch (Throwable t)
159                           {
160                             log.warn("deliverMessage()", t);
161                           }
162                       }
163                   }
164               }
165           }
166       }
167     
168     /***********************************************************************************************************************************************************
169      * Returns the listeners subscribed to the given topic, creating the list if needed.
170      * @param   <T>       the static type of the topic
171      * @param   topic     the topic
172      * @return            the listeners
173      **********************************************************************************************************************************************************/
174     @Nonnull
175     private <T> List<WeakReference<Listener<T>>> findListenersByTopic (@Nonnull final Class<T> topic)
176       {
177         return (List)listenersMapByTopic.computeIfAbsent(topic, t -> new CopyOnWriteArrayList<>());
178       }
179   }