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 }