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