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&lt;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    }