001    /* NotificationBroadcasterSupport.java -- Supporting implementation.
002       Copyright (C) 2007 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package javax.management;
039    
040    import gnu.javax.management.ListenerData;
041    
042    import java.util.ArrayList;
043    import java.util.Iterator;
044    import java.util.List;
045    
046    import 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     */
073    public 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