001 /* 002 * Copyright 2005 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.station.server; 020 021 import java.rmi.RemoteException; 022 import java.rmi.server.UnicastRemoteObject; 023 import java.util.EventObject; 024 import java.util.EventListener; 025 import java.util.List; 026 import java.util.LinkedList; 027 import java.util.Map; 028 import java.util.WeakHashMap; 029 030 import net.dpml.util.Logger; 031 import net.dpml.transit.monitor.LoggingAdapter; 032 033 /** 034 * A abstract base class that established an event queue and handles event dispatch 035 * operations for listeners declared in classes extending this base class. 036 * 037 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 038 * @version 1.0.5 039 */ 040 public abstract class DefaultModel extends UnicastRemoteObject 041 { 042 // ------------------------------------------------------------------------ 043 // state 044 // ------------------------------------------------------------------------ 045 046 /** 047 * Internal logging channel. 048 */ 049 private final Logger m_logger; 050 051 private Map m_listeners = new WeakHashMap(); 052 053 /** 054 * Internal synchronization lock. 055 */ 056 private final Object m_lock = new Object(); 057 058 // ------------------------------------------------------------------------ 059 // constructor 060 // ------------------------------------------------------------------------ 061 062 /** 063 * Creation of a new model. 064 * @param name the name used to construct a logging channel 065 * @exception RemoteException if a remote exception occurs 066 */ 067 public DefaultModel( String name ) 068 throws RemoteException 069 { 070 this( getLoggerForCategory( name ) ); 071 } 072 073 /** 074 * Creation of a new model. 075 * @param logger the assigned logging channel 076 * @exception NullPointerException if the supplied logging channel is null 077 * @exception RemoteException if a remote exception occurs 078 */ 079 public DefaultModel( Logger logger ) 080 throws NullPointerException, RemoteException 081 { 082 super(); 083 084 if( null == logger ) 085 { 086 throw new NullPointerException( "logger" ); 087 } 088 m_logger = logger; 089 } 090 091 // ------------------------------------------------------------------------ 092 // DefaultModel 093 // ------------------------------------------------------------------------ 094 095 /** 096 * Abstract method that must be implemented by classes extending this class. 097 * An implementation is responsible for handling the processing of events 098 * it is aware of or throwing an llegalArgumentException in the case of 099 * unrecognized event types. A typical implementation is shown in the following 100 * code fragment: 101 * 102 * <pre> 103 * protected void processEvent( EventObject eventObject ) 104 * { 105 * if( eventObject instanceof ProxyEvent ) 106 * { 107 * ProxyEvent event = (ProxyEvent) eventObject; 108 * processProxyEvent( event ); 109 * } 110 * else 111 * { 112 * final String error = 113 * "Event class not recognized: " + eventObject.getClass().getName(); 114 * throw new IllegalArgumentException( error ); 115 * } 116 * } 117 * 118 * private void processProxyEvent( ProxyEvent event ) 119 * { 120 * EventListener[] listeners = super.listeners(); 121 * for( int i=0; i<listeners.length; i++ ) 122 * { 123 * EventListener listener = listeners[i]; 124 * if( listener instanceof ProxyListener ) 125 * { 126 * ProxyListener pl = (ProxyListener) listener; 127 * try 128 * { 129 * pl.proxyChanged( event ); 130 * } 131 * catch( Throwable e ) 132 * { 133 * final String error = 134 * "Proxy listener notification error."; 135 * getLogger().error( error, e ); 136 * } 137 * } 138 * } 139 * } 140 * </pre> 141 * 142 * @param event the event to process 143 */ 144 protected abstract void processEvent( EventObject event ); 145 146 /** 147 * Return the assigned logging channel. 148 * @return the logging channel 149 */ 150 protected Logger getLogger() 151 { 152 return m_logger; 153 } 154 155 /** 156 * Add a listener to the set of listeners handled by the model. 157 * @param listener the event listener 158 * @exception NullPointerException if the supplied listener is null 159 */ 160 protected void addListener( EventListener listener ) throws NullPointerException 161 { 162 if( null == listener ) 163 { 164 throw new NullPointerException( "listener" ); 165 } 166 synchronized( m_lock ) 167 { 168 m_listeners.put( listener, null ); 169 } 170 Logger logger = getLogger(); 171 startEventDispatchThread( logger ); 172 } 173 174 /** 175 * Remove a listener to the set of listeners handled by this producer. 176 * @param listener the event listener 177 * @exception NullPointerException if the supplied listener is null 178 */ 179 protected void removeListener( EventListener listener ) throws NullPointerException 180 { 181 if( null == listener ) 182 { 183 throw new NullPointerException( "listener" ); 184 } 185 186 synchronized( m_lock ) 187 { 188 m_listeners.remove( listener ); 189 } 190 } 191 192 /** 193 * Queue of pending notification events. When an event for which 194 * there are one or more listeners occurs, it is placed on this queue 195 * and the queue is notified. A background thread waits on this queue 196 * and delivers the events. This decouples event delivery from 197 * the application concern, greatly simplifying locking and reducing 198 * opportunity for deadlock. 199 */ 200 private static final List EVENT_QUEUE = new LinkedList(); 201 202 /** 203 * A single background thread ("the event notification thread") monitors 204 * the event queue and delivers events that are placed on the queue. 205 */ 206 private static class EventDispatchThread extends Thread 207 { 208 private Logger m_logger; 209 210 EventDispatchThread( Logger logger ) 211 { 212 m_logger = logger; 213 } 214 215 private Logger getLogger() 216 { 217 return m_logger; 218 } 219 220 public void run() 221 { 222 while( true ) 223 { 224 // Wait on EVENT_QUEUE till an event is present 225 EventObject event = null; 226 synchronized( EVENT_QUEUE ) 227 { 228 try 229 { 230 while( EVENT_QUEUE.isEmpty() ) 231 { 232 EVENT_QUEUE.wait(); 233 } 234 Object object = EVENT_QUEUE.remove( 0 ); 235 try 236 { 237 event = (EventObject) object; 238 } 239 catch( ClassCastException cce ) 240 { 241 final String error = 242 "Unexpected class cast exception while processing an event." 243 + "\nEvent: " + object; 244 throw new IllegalStateException( error ); 245 } 246 } 247 catch( InterruptedException e ) 248 { 249 return; 250 } 251 } 252 253 Object source = event.getSource(); 254 if( source instanceof DefaultModel ) 255 { 256 DefaultModel producer = (DefaultModel) source; 257 try 258 { 259 producer.processEvent( event ); 260 } 261 catch( Throwable e ) 262 { 263 final String error = 264 "Unexpected error while processing event." 265 + "\nEvent: " + event 266 + "\nSource: " + this; 267 getLogger().error( error ); 268 } 269 } 270 else 271 { 272 final String error = 273 "Event source is not an instance of " 274 + DefaultModel.class.getName(); 275 throw new IllegalStateException( error ); 276 } 277 } 278 } 279 } 280 281 private static Thread m_EVENT_DISPATCH_THREAD = null; 282 283 /** 284 * This method starts the event dispatch thread the first time it 285 * is called. The event dispatch thread will be started only 286 * if someone registers a listener. 287 */ 288 private static synchronized void startEventDispatchThread( Logger logger ) 289 { 290 if( m_EVENT_DISPATCH_THREAD == null ) 291 { 292 m_EVENT_DISPATCH_THREAD = new EventDispatchThread( logger ); 293 m_EVENT_DISPATCH_THREAD.setDaemon( true ); 294 m_EVENT_DISPATCH_THREAD.start(); 295 } 296 } 297 298 /** 299 * Return the internal synchronization lock object. 300 * @return the lock object 301 */ 302 protected Object getLock() 303 { 304 return m_lock; 305 } 306 307 /** 308 * Return the set of registered listeners. 309 * @return an array of registered listeners 310 */ 311 protected EventListener[] listeners() 312 { 313 synchronized( m_lock ) 314 { 315 return (EventListener[]) m_listeners.keySet().toArray( new EventListener[0] ); 316 } 317 } 318 319 /** 320 * Enqueue an event for delivery to registered 321 * listeners unless there are no registered 322 * listeners. 323 * 324 * @param event the event object to add to the queue 325 */ 326 protected void enqueueEvent( EventObject event ) 327 { 328 enqueueEvent( event, true ); 329 } 330 331 /** 332 * Enqueue an event for delivery to registered 333 * listeners unless there are no registered 334 * listeners. 335 * 336 * @param event the event object to add to the queue 337 * @param asynchronouse TRUE if asynchronouse delivery 338 */ 339 protected void enqueueEvent( EventObject event, boolean asynchronouse ) 340 { 341 if( m_listeners.size() != 0 ) 342 { 343 if( asynchronouse ) 344 { 345 synchronized( EVENT_QUEUE ) 346 { 347 EVENT_QUEUE.add( event ); 348 EVENT_QUEUE.notify(); 349 } 350 } 351 else 352 { 353 processEvent( event ); 354 } 355 } 356 } 357 358 /** 359 * Return a logging channel for the supplied name. 360 * @param name the name to use in construction of the logging channel 361 * @return the logging channel 362 */ 363 static Logger getLoggerForCategory( String name ) 364 { 365 if( null == name ) 366 { 367 return new LoggingAdapter( "" ); 368 } 369 else 370 { 371 return new LoggingAdapter( name ); 372 } 373 } 374 }