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