001    /*
002     * Copyright 2005-2006 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.util;
020    
021    import java.rmi.RemoteException;
022    import java.rmi.server.UnicastRemoteObject;
023    import java.rmi.NoSuchObjectException;
024    import java.util.EventObject;
025    import java.util.EventListener;
026    import java.util.WeakHashMap;
027    
028    /**
029     * A abstract base class that established an event queue and handles event dispatch 
030     * operations for listeners declared in a class extending this base class.
031     *
032     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
033     * @version 1.0.3
034     */
035    public abstract class UnicastEventSource extends UnicastRemoteObject implements EventHandler
036    {
037       /**
038        * Internal synchronization lock.
039        */
040        private final Object m_lock = new Object();
041        
042       /**
043        * The controller that provides the main event dispath thread.
044        */
045        private final EventQueue m_queue;
046        
047        private final WeakHashMap m_listeners = new WeakHashMap();
048        
049        private final Logger m_logger;
050    
051       /**
052        * Creation of a new <tt>UnicastEventSource</tt>.
053        * @param queue the event queue
054        * @param logger the assigned logging channel
055        * @exception RemoteException if a remote I/O exception occurs
056        */
057        protected UnicastEventSource( EventQueue queue, Logger logger ) throws RemoteException
058        {
059            super();
060            m_queue = queue;
061            m_logger = logger;
062        }
063        
064        //--------------------------------------------------------------------------
065        // internal
066        //--------------------------------------------------------------------------
067        
068       /**
069        * Return the logging channel assigned to the event source.
070        * @return the logging channel
071        */
072        protected Logger getLocalLogger()
073        {
074            return m_logger;
075        }
076        
077       /**
078        * Return the event queue.
079        * @return the queue
080        */
081        protected EventQueue getEventQueue()
082        {
083            return m_queue;
084        }
085        
086       /**
087        * Abstract operation to be implemented by classes extending this base class.
088        * An implementation is reposible for the posting of the event to associated 
089        * listeners.  Event posting will be executed under a separate thread to the 
090        * thread that initiated the event post.
091        *
092        * @param event the event to process
093        */
094        public abstract void processEvent( EventObject event );
095    
096       /**
097        * Add a listener to the set of listeners handled by this producer.
098        * @param listener the event listener
099        */
100        protected void addListener( EventListener listener ) 
101        {
102            if( null == listener )
103            {
104                throw new NullPointerException( "listener" );
105            }
106            synchronized( m_lock ) 
107            {
108                m_listeners.put( listener, null );
109            }
110        }
111    
112       /**
113        * Remove a listener to the set of listeners handled by this producer.
114        * @param listener the event listener
115        */
116        protected void removeListener( EventListener listener )
117        {
118            if( null == listener )
119            {
120                throw new NullPointerException( "listener" );
121            }
122            synchronized( m_lock )
123            {
124                m_listeners.remove( listener );
125            }
126        }
127    
128        
129       /**
130        * Return the array of registered event listeners.
131        *
132        * @return the event listeners
133        */
134        public EventListener[] getEventListeners() 
135        {
136            synchronized( m_lock )
137            {
138                return (EventListener[]) m_listeners.keySet().toArray( new EventListener[0] );
139            }
140        }
141    
142       /**
143        * Enqueue an event for delivery to registered
144        * listeners unless there are no registered
145        * listeners.
146        * @param event the event to enqueue
147        */
148        protected void enqueueEvent( EventObject event )
149        {
150            if( m_listeners.size() > 0 )
151            {
152                m_queue.enqueueEvent( event );
153            }
154        }
155        
156       /**
157        * Return the internal synchronization lock.
158        * @return the lock object
159        */
160        protected Object getLock()
161        {
162            return m_lock;
163        }
164        
165       /**
166        * Terminate the event source.
167        */
168        public void terminate()
169        {
170            synchronized( m_lock )
171            {
172                EventListener[] listeners = getEventListeners();
173                for( int i=0; i < listeners.length; i++ )
174                {
175                    EventListener listener = listeners[i];
176                    removeListener( listener );
177                }
178            }
179            
180            Thread thread = new Terminator( this, m_logger );
181            thread.start();
182        }
183        
184       /**
185        * Internal class that handles instance retraction for the RMI runtime.
186        */
187        private class Terminator extends Thread
188        {
189            private final UnicastEventSource m_source;
190            private final Logger m_logger;
191            
192           /**
193            * Internal class terminator that handles unexport of an event source
194            * under a separate thread.
195            * @param source the event source instance
196            * @param logger the event source logger
197            */
198            Terminator( UnicastEventSource source, Logger logger )
199            {
200                m_source = source;
201                m_logger = logger;
202            }
203            
204           /**
205            * Terminator execution.
206            */
207            public void run()
208            {
209                try
210                {
211                    UnicastRemoteObject.unexportObject( m_source, true );
212                    m_logger.debug( "terminated" );
213                }
214                catch( NoSuchObjectException e )
215                {
216                    boolean ignoreThis = true; // objct has not been exported
217                }
218                catch( RemoteException e )
219                {
220                    final String error = 
221                      "Unexpected remote exception while retracting component handler remote reference.";
222                    m_logger.warn( error, e );
223                }
224            }
225        }
226    }