001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019 020package org.apache.wiki.event; 021 022import java.lang.ref.WeakReference; 023import java.util.*; 024 025import org.apache.log4j.Logger; 026 027/** 028 * A singleton class that manages the addition and removal of WikiEvent 029 * listeners to a event source, as well as the firing of events to those 030 * listeners. An "event source" is the object delegating its event 031 * handling to an inner delegating class supplied by this manager. The 032 * class being serviced is considered a "client" of the delegate. The 033 * WikiEventManager operates across any number of simultaneously-existing 034 * WikiEngines since it manages all delegation on a per-object basis. 035 * Anything that might fire a WikiEvent (or any of its subclasses) can be 036 * a client. 037 * </p> 038 * 039 * <h3>Using a Delegate for Event Listener Management</h3> 040 * <p> 041 * Basically, rather than have all manner of client classes maintain their 042 * own listener lists, add and remove listener methods, any class wanting 043 * to attach listeners can simply request a delegate object to provide that 044 * service. The delegate handles the listener list, the add and remove 045 * listener methods. Firing events is then a matter of calling the 046 * WikiEventManager's {@link #fireEvent(Object,WikiEvent)} method, where 047 * the Object is the client. Prior to instantiating the event object, the 048 * client can call {@link #isListening(Object)} to see there are any 049 * listeners attached to its delegate. 050 * </p> 051 * 052 * <h3>Adding Listeners</h3> 053 * <p> 054 * Adding a WikiEventListener to an object is very simple: 055 * </p> 056 * <pre> 057 * WikiEventManager.addWikiEventListener(object,listener); 058 * </pre> 059 * 060 * <h3>Removing Listeners</h3> 061 * <p> 062 * Removing a WikiEventListener from an object is very simple: 063 * </p> 064 * <pre> 065 * WikiEventManager.removeWikiEventListener(object,listener); 066 * </pre> 067 * If you only have a reference to the listener, the following method 068 * will remove it from any clients managed by the WikiEventManager: 069 * <pre> 070 * WikiEventManager.removeWikiEventListener(listener); 071 * </pre> 072 * 073 * <h3>Backward Compatibility: Replacing Existing <tt>fireEvent()</tt> Methods</h3> 074 * <p> 075 * Using one manager for all events processing permits consolidation of all event 076 * listeners and their associated methods in one place rather than having them 077 * attached to specific subcomponents of an application, and avoids a great deal 078 * of event-related cut-and-paste code. Convenience methods that call the 079 * WikiEventManager for event delegation can be written to maintain existing APIs. 080 * </p> 081 * <p> 082 * For example, an existing <tt>fireEvent()</tt> method might look something like 083 * this: 084 * </p> 085 * <pre> 086 * protected final void fireEvent( WikiEvent event ) 087 * { 088 * for ( Iterator it = m_listeners.iterator(); it.hasNext(); ) 089 * { 090 * WikiEventListener listener = (WikiEventListener)it.next(); 091 * listener.actionPerformed(event); 092 * } 093 * } 094 * </pre> 095 * <p> 096 * One disadvantage is that the above method is supplied with event objects, 097 * which are created even when no listener exists for them. In a busy wiki 098 * with many users unused/unnecessary event creation could be considerable. 099 * Another advantage is that in addition to the iterator, there must be code 100 * to support the addition and remove of listeners. The above could be 101 * replaced with the below code (and with no necessary local support for 102 * adding and removing listeners): 103 * </p> 104 * <pre> 105 * protected final void fireEvent( int type ) 106 * { 107 * if ( WikiEventManager.isListening(this) ) 108 * { 109 * WikiEventManager.fireEvent(this,new WikiEngineEvent(this,type)); 110 * } 111 * } 112 * </pre> 113 * <p> 114 * This only needs to be customized to supply the specific parameters for 115 * whatever WikiEvent you want to create. 116 * </p> 117 * 118 * <h3 id="preloading">Preloading Listeners</h3> 119 * <p> 120 * This may be used to create listeners for objects that don't yet exist, 121 * particularly designed for embedded applications that need to be able 122 * to listen for the instantiation of an Object, by maintaining a cache 123 * of client-less WikiEvent sources that set their client upon being 124 * popped from the cache. Each time any of the methods expecting a client 125 * parameter is called with a null parameter it will preload an internal 126 * cache with a client-less delegate object that will be popped and 127 * returned in preference to creating a new object. This can have unwanted 128 * side effects if there are multiple clients populating the cache with 129 * listeners. The only check is for a Class match, so be aware if others 130 * might be populating the client-less cache with listeners. 131 * </p> 132 * <h3>Listener lifecycle</h3> 133 * <p> 134 * Note that in most cases it is not necessary to remove a listener. 135 * As of 2.4.97, the listeners are stored as WeakReferences, and will be 136 * automatically cleaned at the next garbage collection, if you no longer 137 * hold a reference to them. Of course, until the garbage is collected, 138 * your object might still be getting events, so if you wish to avoid that, 139 * please remove it explicitly as described above. 140 * </p> 141 * @since 2.4.20 142 */ 143public final class WikiEventManager 144{ 145 private static final Logger log = Logger.getLogger(WikiEventManager.class); 146 147 /* If true, permits a WikiEventMonitor to be set. */ 148 private static boolean c_permitMonitor = false; 149 150 /* Optional listener to be used as all-event monitor. */ 151 private static WikiEventListener c_monitor = null; 152 153 /* The Map of client object to WikiEventDelegate. */ 154 private final Map<Object, WikiEventDelegate> m_delegates = new HashMap<Object, WikiEventDelegate>(); 155 156 /* The Vector containing any preloaded WikiEventDelegates. */ 157 private final Vector<WikiEventDelegate> m_preloadCache = new Vector<WikiEventDelegate>(); 158 159 /* Singleton instance of the WikiEventManager. */ 160 private static WikiEventManager c_instance = null; 161 162 // ............ 163 164 /** 165 * Constructor for a WikiEventManager. 166 */ 167 private WikiEventManager() 168 { 169 c_instance = this; 170 log.debug("instantiated WikiEventManager"); 171 } 172 173 /** 174 * As this is a singleton class, this returns the single 175 * instance of this class provided with the property file 176 * filename and bit-wise application settings. 177 * 178 * @return A shared instance of the WikiEventManager 179 */ 180 public static WikiEventManager getInstance() 181 { 182 if( c_instance == null ) 183 { 184 synchronized( WikiEventManager.class ) 185 { 186 WikiEventManager mgr = new WikiEventManager(); 187 // start up any post-instantiation services here 188 return mgr; 189 } 190 } 191 return c_instance; 192 } 193 194 195 // public/API methods ...................................................... 196 197 198 /** 199 * Registers a WikiEventListener with a WikiEventDelegate for 200 * the provided client object. 201 * 202 * <h3>Monitor Listener</h3> 203 * 204 * If <tt>client</tt> is a reference to the WikiEventManager class 205 * itself and the compile-time flag {@link #c_permitMonitor} is true, 206 * this attaches the listener as an all-event monitor, overwriting 207 * any previous listener (hence returning true). 208 * <p> 209 * You can remove any existing monitor by either calling this method 210 * with <tt>client</tt> as a reference to this class and the 211 * <tt>listener</tt> parameter as null, or 212 * {@link #removeWikiEventListener(Object,WikiEventListener)} with a 213 * <tt>client</tt> as a reference to this class. The <tt>listener</tt> 214 * parameter in this case is ignored. 215 * 216 * @param client the client of the event source 217 * @param listener the event listener 218 * @return true if the listener was added (i.e., it was not already in the list and was added) 219 */ 220 public static boolean addWikiEventListener( 221 Object client, WikiEventListener listener ) 222 { 223 if ( client == WikiEventManager.class ) 224 { 225 if ( c_permitMonitor ) c_monitor = listener; 226 return c_permitMonitor; 227 } 228 WikiEventDelegate delegate = getInstance().getDelegateFor(client); 229 return delegate.addWikiEventListener(listener); 230 } 231 232 233 /** 234 * Un-registers a WikiEventListener with the WikiEventDelegate for 235 * the provided client object. 236 * 237 * @param client the client of the event source 238 * @param listener the event listener 239 * @return true if the listener was found and removed. 240 */ 241 public static boolean removeWikiEventListener( 242 Object client, WikiEventListener listener ) 243 { 244 if ( client == WikiEventManager.class ) 245 { 246 c_monitor = null; 247 return true; 248 } 249 WikiEventDelegate delegate = getInstance().getDelegateFor(client); 250 return delegate.removeWikiEventListener(listener); 251 } 252 253 254 /** 255 * Return the Set containing the WikiEventListeners attached to the 256 * delegate for the supplied client. If there are no attached listeners, 257 * returns an empty Iterator rather than null. Note that this will 258 * create a delegate for the client if it did not exist prior to the call. 259 * 260 * <h3>NOTE</h3> 261 * <p> 262 * This method returns a Set rather than an Iterator because of the high 263 * likelihood of the Set being modified while an Iterator might be active. 264 * This returns an unmodifiable reference to the actual Set containing 265 * the delegate's listeners. Any attempt to modify the Set will throw an 266 * {@link java.lang.UnsupportedOperationException}. This method is not 267 * synchronized and it should be understood that the composition of the 268 * backing Set may change at any time. 269 * </p> 270 * 271 * @param client the client of the event source 272 * @return an unmodifiable Set containing the WikiEventListeners attached to the client 273 * @throws java.lang.UnsupportedOperationException if any attempt is made to modify the Set 274 */ 275 public static Set getWikiEventListeners( Object client ) 276 throws UnsupportedOperationException 277 { 278 WikiEventDelegate delegate = getInstance().getDelegateFor(client); 279 return delegate.getWikiEventListeners(); 280 } 281 282 283 /** 284 * Un-registers a WikiEventListener from any WikiEventDelegate client managed by this 285 * WikiEventManager. Since this is a one-to-one relation, the first match will be 286 * returned upon removal; a true return value indicates the WikiEventListener was 287 * found and removed. 288 * 289 * @param listener the event listener 290 * @return true if the listener was found and removed. 291 */ 292 public static boolean removeWikiEventListener( WikiEventListener listener ) 293 { 294 // get the Map.entry object for the entire Map, then check match on entry (listener) 295 WikiEventManager mgr = getInstance(); 296 Map sources = mgr.getDelegates(); 297 synchronized( sources ) 298 { 299 // get an iterator over the Map.Enty objects in the map 300 Iterator it = sources.entrySet().iterator(); 301 while( it.hasNext() ) 302 { 303 Map.Entry entry = (Map.Entry)it.next(); 304 // the entry value is the delegate 305 WikiEventDelegate delegate = (WikiEventDelegate)entry.getValue(); 306 307 // now see if we can remove the listener from the delegate 308 // (delegate may be null because this is a weak reference) 309 if( delegate != null && delegate.removeWikiEventListener(listener) ) 310 { 311 return true; // was removed 312 } 313 } 314 } 315 return false; 316 } 317 318 319 /** 320 * Returns true if there are one or more listeners registered with 321 * the provided client Object (undelegated event source). This locates 322 * any delegate and checks to see if it has any listeners attached. 323 * 324 * @param client the client Object 325 * @return True, if there is a listener for this client object. 326 */ 327 public static boolean isListening( Object client ) 328 { 329 WikiEventDelegate source = getInstance().getDelegateFor(client); 330 return source != null ? source.isListening() : false ; 331 } 332 333 334 /** 335 * Notify all listeners of the WikiEventDelegate having a registered 336 * interest in change events of the supplied WikiEvent. 337 * 338 * @param client the client initiating the event. 339 * @param event the WikiEvent to fire. 340 */ 341 public static void fireEvent( Object client, WikiEvent event ) 342 { 343 WikiEventDelegate source = getInstance().getDelegateFor(client); 344 if ( source != null ) source.fireEvent(event); 345 if ( c_monitor != null ) c_monitor.actionPerformed(event); 346 } 347 348 349 // private and utility methods ............................................. 350 351 352 /** 353 * Return the client-to-delegate Map. 354 */ 355 private Map getDelegates() 356 { 357 return m_delegates; 358 } 359 360 361 /** 362 * Returns a WikiEventDelegate for the provided client Object. 363 * If the parameter is a class reference, will generate and return a 364 * client-less WikiEventDelegate. If the parameter is not a Class and 365 * the delegate cache contains any objects matching the Class of any 366 * delegates in the cache, the first Class-matching delegate will be 367 * used in preference to creating a new delegate. 368 * If a null parameter is supplied, this will create a client-less 369 * delegate that will attach to the first incoming client (i.e., 370 * there will be no Class-matching requirement). 371 * 372 * @param client the client Object, or alternately a Class reference 373 * @return the WikiEventDelegate. 374 */ 375 private WikiEventDelegate getDelegateFor( Object client ) 376 { 377 synchronized( m_delegates ) 378 { 379 if( client == null || client instanceof Class ) // then preload the cache 380 { 381 WikiEventDelegate delegate = new WikiEventDelegate(client); 382 m_preloadCache.add(delegate); 383 m_delegates.put( client, delegate ); 384 return delegate; 385 } 386 else if( !m_preloadCache.isEmpty() ) 387 { 388 // then see if any of the cached delegates match the class of the incoming client 389 for( int i = m_preloadCache.size()-1 ; i >= 0 ; i-- ) // start with most-recently added 390 { 391 WikiEventDelegate delegate = m_preloadCache.elementAt(i); 392 if( delegate.getClientClass() == null 393 || delegate.getClientClass().equals(client.getClass()) ) 394 { 395 // we have a hit, so use it, but only on a client we haven't seen before 396 if( !m_delegates.keySet().contains(client) ) 397 { 398 m_preloadCache.remove(delegate); 399 m_delegates.put( client, delegate ); 400 return delegate; 401 } 402 } 403 } 404 } 405 // otherwise treat normally... 406 WikiEventDelegate delegate = m_delegates.get( client ); 407 if( delegate == null ) 408 { 409 delegate = new WikiEventDelegate( client ); 410 m_delegates.put( client, delegate ); 411 } 412 return delegate; 413 } 414 } 415 416 417 // ......................................................................... 418 419 420 /** 421 * Inner delegating class that manages event listener addition and 422 * removal. Classes that generate events can obtain an instance of 423 * this class from the WikiEventManager and delegate responsibility 424 * to it. Interaction with this delegating class is done via the 425 * methods of the {@link WikiEventDelegate} API. 426 * 427 * @since 2.4.20 428 */ 429 private static final class WikiEventDelegate 430 { 431 /* A list of event listeners for this instance. */ 432 433 private ArrayList<WeakReference<WikiEventListener>> m_listenerList = new ArrayList<WeakReference<WikiEventListener>>(); 434 435 private Class m_class = null; 436 437 /** 438 * Constructor for an WikiEventDelegateImpl, provided 439 * with the client Object it will service, or the Class 440 * of client, the latter when used to preload a future 441 * incoming delegate. 442 */ 443 protected WikiEventDelegate( Object client ) 444 { 445 if( client instanceof Class ) 446 { 447 m_class = (Class)client; 448 } 449 } 450 451 /** 452 * Returns the class of the client-less delegate, null if 453 * this delegate is attached to a client Object. 454 */ 455 protected Class getClientClass() 456 { 457 return m_class; 458 } 459 460 461 /** 462 * Return an unmodifiable Set containing the WikiEventListeners of 463 * this WikiEventDelegateImpl. If there are no attached listeners, 464 * returns an empty Set rather than null. 465 * 466 * @return an unmodifiable Set containing this delegate's WikiEventListeners 467 * @throws java.lang.UnsupportedOperationException if any attempt is made to modify the Set 468 */ 469 public Set getWikiEventListeners() 470 { 471 synchronized( m_listenerList ) 472 { 473 TreeSet<WikiEventListener> set = new TreeSet<WikiEventListener>( new WikiEventListenerComparator() ); 474 475 for( Iterator i = m_listenerList.iterator(); i.hasNext(); ) 476 { 477 WikiEventListener l = (WikiEventListener) ((WeakReference)i.next()).get(); 478 479 if( l != null ) 480 { 481 set.add( l ); 482 } 483 } 484 485 return Collections.unmodifiableSet(set); 486 } 487 } 488 489 490 /** 491 * Adds <tt>listener</tt> as a listener for events fired by the WikiEventDelegate. 492 * 493 * @param listener the WikiEventListener to be added 494 * @return true if the listener was added (i.e., it was not already in the list and was added) 495 */ 496 public boolean addWikiEventListener( WikiEventListener listener ) 497 { 498 synchronized( m_listenerList ) 499 { 500 return m_listenerList.add( new WeakReference<WikiEventListener>(listener) ); 501 } 502 } 503 504 505 /** 506 * Removes <tt>listener</tt> from the WikiEventDelegate. 507 * 508 * @param listener the WikiEventListener to be removed 509 * @return true if the listener was removed (i.e., it was actually in the list and was removed) 510 */ 511 public boolean removeWikiEventListener( WikiEventListener listener ) 512 { 513 synchronized( m_listenerList ) 514 { 515 for( Iterator i = m_listenerList.iterator(); i.hasNext(); ) 516 { 517 WikiEventListener l = (WikiEventListener) ((WeakReference)i.next()).get(); 518 519 if( l == listener ) 520 { 521 i.remove(); 522 return true; 523 } 524 } 525 } 526 527 return false; 528 } 529 530 531 /** 532 * Returns true if there are one or more listeners registered 533 * with this instance. 534 */ 535 public boolean isListening() 536 { 537 synchronized( m_listenerList ) 538 { 539 return !m_listenerList.isEmpty(); 540 } 541 } 542 543 544 /** 545 * Notify all listeners having a registered interest 546 * in change events of the supplied WikiEvent. 547 */ 548 public void fireEvent( WikiEvent event ) 549 { 550 boolean needsCleanup = false; 551 552 try 553 { 554 synchronized( m_listenerList ) 555 { 556 for( int i = 0; i < m_listenerList.size(); i++ ) 557 { 558 WikiEventListener listener = (WikiEventListener) ((WeakReference)m_listenerList.get(i)).get(); 559 560 if( listener != null ) 561 { 562 listener.actionPerformed( event ); 563 } 564 else 565 { 566 needsCleanup = true; 567 } 568 } 569 570 // 571 // Remove all such listeners which have expired 572 // 573 if( needsCleanup ) 574 { 575 for( int i = 0; i < m_listenerList.size(); i++ ) 576 { 577 WeakReference w = m_listenerList.get(i); 578 579 if( w.get() == null ) m_listenerList.remove(i--); 580 } 581 } 582 583 } 584 } 585 catch( ConcurrentModificationException e ) 586 { 587 // 588 // We don't die, we just don't do notifications in that case. 589 // 590 log.info("Concurrent modification of event list; please report this.",e); 591 } 592 593 } 594 595 } // end inner class WikiEventDelegate 596 597 private static class WikiEventListenerComparator implements Comparator<WikiEventListener> 598 { 599 // TODO: This method is a critical performance bottleneck 600 public int compare(WikiEventListener w0, WikiEventListener w1) 601 { 602 if( w1 == w0 || w0.equals(w1) ) return 0; 603 604 return w1.hashCode() - w0.hashCode(); 605 } 606 } 607} // end org.apache.wiki.event.WikiEventManager