001/* NotificationBroadcasterSupport.java -- Supporting implementation.
002   Copyright (C) 2007 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038package javax.management;
039
040import gnu.javax.management.ListenerData;
041
042import java.util.ArrayList;
043import java.util.Iterator;
044import java.util.List;
045
046import java.util.concurrent.Executor;
047
048/**
049 * <p>
050 * Provides an implementation of the {@link NotificationEmitter}
051 * interface, which beans may utilise by extension.  By default,
052 * a synchronous dispatch system is provided, whereby the
053 * {@link #handleNotification(NotificationListener, Notification,
054 * Object)} is called once per listener by
055 * {*@link #sendNotification(Notification)} before returning.
056 * Thus, unless the listener is remote, it will have received
057 * the notification before the method returns.
058 * This may be changed by overriding the <code>handleNotification</code>
059 * method, or by providing an {@link java.util.concurrent.Executor} to
060 * use.  With the latter, the dispatch of a notification to a particular
061 * listener will form one task performed by the executor.
062 * </p>
063 * <p>
064 * Any exceptions thrown by the dispatch process will be caught, allowing
065 * other listeners to still receive the notification.  However, any
066 * {@link Error}s that are thrown will be propogated to the caller of
067 * {@link #sendNotification(Notification)}.
068 * </p>
069 *
070 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
071 * @since 1.5
072 */
073public class NotificationBroadcasterSupport
074  implements NotificationEmitter
075{
076
077  /**
078   * The executor for dispatch, or
079   * <code>null</code> if this thread should
080   * handle dispatch itself.
081   */
082  private Executor executor;
083
084  /**
085   * An array containing information on each
086   * notification, or <code>null</code> if an
087   * empty array should be returned by
088   * {@link #getNotificationInfo()}.
089   */
090  private MBeanNotificationInfo[] info;
091
092  /**
093   * The list of listeners registered with
094   * this broadcaster.
095   */
096  private final List<ListenerData> listeners =
097    new ArrayList<ListenerData>();
098
099  /**
100   * Constructs a {@link NotificationBroadcasterSupport} using
101   * the default synchronous dispatch model, where a single
102   * thread sends the notification to all listeners.  This
103   * is equivalent to calling
104   * <code>NotificationBroadcasterSupport(null, null)</code>.
105   */
106  public NotificationBroadcasterSupport()
107  {
108    this(null, (MBeanNotificationInfo[]) null);
109  }
110
111  /**
112   * Constructs a {@link NotificationBroadcasterSupport} where
113   * the specified (@link java.util.concurrent.Executor} is used
114   * to perform each invocation of the
115   * {@link #handleNotification(NotificationListener, Notification,
116   * Object)} method.  Filtering is performed beforehand, by this
117   * thread; only calls which have successfully passed through the
118   * filter are sent to the executor.  This is equivalent to calling
119   * <code>NotificationBroadcasterSupport(executor, null)</code>.
120   * 
121   * @param executor the executor to use for each call to
122   *                 <code>handleNotification()</code>.
123   * @since 1.6
124   */
125  public NotificationBroadcasterSupport(Executor executor)
126  {
127    this(executor, (MBeanNotificationInfo) null);
128  }
129
130  /**
131   * Constructs a {@link NotificationBroadcasterSupport} using
132   * the default synchronous dispatch model, where a single
133   * thread sends the notification to all listeners. The specified
134   * {@link MBeanNotificationInfo} array is used to provide
135   * information about the notifications on calls to
136   * {@link #getNotificationInfo()}, where a clone will be
137   * returned if the array is non-empty.   This is equivalent to
138   * calling <code>NotificationBroadcasterSupport(null, info)</code>.
139   *
140   * @param info an array of {@link MBeanNotificationInfo} objects
141   *             describing the notifications delivered by this 
142   *             broadcaster.  This may be <code>null</code>, which
143   *             is taken as being equivalent to an empty array.
144   */
145  public NotificationBroadcasterSupport(MBeanNotificationInfo... info)
146  {
147    this(null, info);
148  }
149
150  /**
151   * Constructs a {@link NotificationBroadcasterSupport} where
152   * the specified (@link java.util.concurrent.Executor} is used
153   * to perform each invocation of the
154   * {@link #handleNotification(NotificationListener, Notification,
155   * Object)} method.  Filtering is performed beforehand, by this
156   * thread; only calls which have successfully passed through the
157   * filter are sent to the executor.  The specified
158   * {@link MBeanNotificationInfo} array is used to provide
159   * information about the notifications on calls to
160   * {@link #getNotificationInfo()}, where a clone will be
161   * returned if the array is non-empty.
162   * 
163   * @param executor the executor to use for each call to
164   *                 <code>handleNotification()</code>.
165   * @param info an array of {@link MBeanNotificationInfo} objects
166   *             describing the notifications delivered by this 
167   *             broadcaster.  This may be <code>null</code>, which
168   *             is taken as being equivalent to an empty array.
169   * @since 1.6
170   */
171  public NotificationBroadcasterSupport(Executor executor,
172                                        MBeanNotificationInfo... info)
173  {
174    this.executor = executor;
175    this.info = info;
176  }
177
178  /**
179   * Registers the specified listener as a new recipient of
180   * notifications from this bean.  If non-null, the filter
181   * argument will be used to select which notifications are
182   * delivered.  The supplied object will also be passed to
183   * the recipient with each notification.  This should not
184   * be modified by the broadcaster, but instead should be
185   * passed unmodified to the listener.
186   *
187   * @param listener the new listener, who will receive
188   *                 notifications from this broadcasting bean.
189   * @param filter a filter to determine which notifications are
190   *               delivered to the listener, or <code>null</code>
191   *               if no filtering is required.
192   * @param passback an object to be passed to the listener with
193   *                 each notification.
194   * @throws IllegalArgumentException if <code>listener</code> is
195   *                                  <code>null</code>.
196   * @see #removeNotificationListener(NotificationListener)
197   */
198  public void addNotificationListener(NotificationListener listener,
199                                      NotificationFilter filter,
200                                      Object passback)
201    throws IllegalArgumentException
202  {
203    if (listener == null)
204      throw new IllegalArgumentException("Null listener added to bean.");
205    listeners.add(new ListenerData(listener, filter, passback));
206  }
207
208  /**
209   * Returns an array describing the notifications this
210   * bean may send to its registered listeners.  Ideally, this
211   * array should be complete, but in some cases, this may
212   * not be possible.  However, be aware that some listeners
213   * may expect this to be so.
214   *
215   * @return the array of possible notifications.
216   */
217  public MBeanNotificationInfo[] getNotificationInfo()
218  {
219    if (info == null || info.length == 0)
220      return new MBeanNotificationInfo[0];
221    return (MBeanNotificationInfo[]) info.clone();
222  }
223
224  /**
225   * This method is called on a per-listener basis, either
226   * from this thread or the supplied executor, and may be
227   * overridden by subclasses which wish to change how
228   * notifications are delivered.  The default
229   * implementation simply calls
230   * <code>listener.handleNotification(notif, passback)</code>.
231   *
232   * @param listener the listener to send the notification to.
233   * @param notif the notification to dispatch.
234   * @param passback the passback object of the listener.
235   */
236  protected void handleNotification(NotificationListener listener,
237                                    Notification notif,
238                                    Object passback)
239  {
240    listener.handleNotification(notif, passback);
241  }
242
243  /**
244   * Removes the specified listener from the list of recipients
245   * of notifications from this bean.  This includes all combinations
246   * of filters and passback objects registered for this listener.
247   * For more specific removal of listeners, see the subinterface
248   * {@link NotificationEmitter}.
249   *
250   * @param listener the listener to remove.
251   * @throws ListenerNotFoundException if the specified listener
252   *                                   is not registered with this bean.
253   * @see #addNotificationListener(NotificationListener, NotificationFilter,
254   *                               java.lang.Object)
255   */
256  public void removeNotificationListener(NotificationListener listener)
257    throws ListenerNotFoundException
258  {
259    Iterator<ListenerData> it = listeners.iterator();
260    boolean foundOne = false;
261    while (it.hasNext())
262      {
263        if (it.next().getListener() == listener)
264          {
265            it.remove();
266            foundOne = true;
267          }
268      }
269    if (!foundOne)
270      throw new ListenerNotFoundException("The specified listener, " + listener +
271                                          "is not registered with this bean.");
272  }
273
274  /**
275   * Removes the specified listener from the list of recipients
276   * of notifications from this bean.  Only the first instance with
277   * the supplied filter and passback object is removed.
278   * <code>null</code> is used as a valid value for these parameters,
279   * rather than as a way to remove all registration instances for
280   * the specified listener; for this behaviour instead, see the details
281   * of the same method in {@link NotificationBroadcaster}.
282   *
283   * @param listener the listener to remove.
284   * @param filter the filter of the listener to remove.
285   * @param passback the passback object of the listener to remove.
286   * @throws ListenerNotFoundException if the specified listener
287   *                                   is not registered with this bean.
288   * @see #addNotificationListener(NotificationListener, NotificationFilter,
289   *                               java.lang.Object)
290   */
291  public void removeNotificationListener(NotificationListener listener,
292                                         NotificationFilter filter,
293                                         Object passback)
294    throws ListenerNotFoundException
295  {
296    if (!(listeners.remove(new ListenerData(listener, filter, passback))))
297      {
298        throw new ListenerNotFoundException("The specified listener, " + listener +
299                                            " with filter " + filter + 
300                                            "and passback " + passback + 
301                                            ", is not registered with this bean.");
302      }
303  }
304
305  /**
306   * <p>
307   * Performs delivery of the notification.  If an executor
308   * was specified on construction, this will be used to call
309   * {@link #handleNotification(NotificationListener, Notification,
310   * Object)}.  If the executor is <code>null</code>, however,
311   * this thread calls the method itself in order to perform a
312   * synchronous dispatch of the notification to all eligible
313   * listeners.
314   * </p>
315   * <p>
316   * Prior to either process taking place, the listeners are filtered.
317   * Notifications are only delivered if the filter is either
318   * <code>null</code> or returns true from the
319   * {@link NotificationFilter#isNotificationEnabled(Notification)}
320   * method.
321   * </p>
322   *
323   * @param notif the notification to send.
324   */
325  public void sendNotification(Notification notif)
326  {
327    for (ListenerData ldata : listeners)
328      {
329        NotificationFilter filter = ldata.getFilter();
330        if (filter == null || filter.isNotificationEnabled(notif))
331          {
332            if (executor == null)
333              try
334                {
335                  handleNotification(ldata.getListener(), notif,
336                                     ldata.getPassback());
337                }
338              catch (Exception e) { /* Ignore */ }
339            else
340              executor.execute(new DispatchTask(ldata, notif));
341          }
342      }
343  }
344
345  /**
346   * The dispatch task to be performed by an executor.
347   */
348  private final class DispatchTask
349    implements Runnable
350  {
351
352    /**
353     * The data on the listener being called.
354     */
355    private ListenerData ldata;
356
357    /**
358     * The notification to send.
359     */
360    private Notification notif;
361
362    /**
363     * Construct a new {@link DispatchTask}.
364     *
365     * @param ldata the listener data.
366     * @param notif the notification to send.
367     */
368    public DispatchTask(ListenerData ldata,
369                        Notification notif)
370    {
371      this.ldata = ldata;
372      this.notif = notif;
373    }
374
375    /**
376     * Dispatch the notification.
377     */
378    public void run()
379    {
380      try
381        {
382          handleNotification(ldata.getListener(), notif,
383                             ldata.getPassback());
384        }
385      catch (Exception e) { /* Ignore */ }
386    }
387  }
388
389}
390