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