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.transit;
020    
021    import java.io.File;
022    import java.net.URL;
023    import java.net.URI;
024    import java.rmi.RemoteException;
025    import java.rmi.NoSuchObjectException;
026    import java.rmi.server.UnicastRemoteObject;
027    import java.util.EventObject;
028    import java.util.EventListener;
029    
030    import net.dpml.transit.info.CacheDirective;
031    import net.dpml.transit.info.ProxyDirective;
032    import net.dpml.transit.info.TransitDirective;
033    import net.dpml.transit.model.CacheModel;
034    import net.dpml.transit.model.ProxyModel;
035    import net.dpml.transit.model.TransitModel;
036    import net.dpml.transit.model.DisposalEvent;
037    import net.dpml.transit.model.DisposalListener;
038    import net.dpml.transit.monitor.LoggingAdapter;
039    
040    import net.dpml.util.EventQueue;
041    import net.dpml.util.Logger;
042    
043    /**
044     * The DefaultTransitModel class maintains an active configuration of the 
045     * Transit system.
046     *
047     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
048     * @version 1.0.3
049     */
050    public class DefaultTransitModel extends DefaultModel implements TransitModel
051    {
052        // ------------------------------------------------------------------------
053        // static
054        // ------------------------------------------------------------------------
055        
056       /**
057        * Default configuration url path.
058        */
059        public static final String DEFAULT_PROFILE_PATH = "local:xml:dpml/transit/standard";
060        
061       /**
062        * Default configuration url path.
063        */
064        public static final URI DEFAULT_PROFILE_URI = createStaticURI( DEFAULT_PROFILE_PATH );
065        
066       /**
067        * System property key used to hold an overriding configuration url.
068        */
069        public static final String PROFILE_KEY = "dpml.transit.profile";
070    
071       /**
072        * Return a model that is restricted to the secure local environment with 
073        * no proxy setting or external hosts.
074        * @param logger the logging channel to assign to the model
075        * @return the transit model
076        */
077        public static DefaultTransitModel getSecureModel( Logger logger )
078        {
079            try
080            {
081                TransitDirective directive = new TransitDirective( null, new CacheDirective() );
082                if( logger.isTraceEnabled() )
083                {
084                    ClassLoader system = ClassLoader.getSystemClassLoader();
085                    int id = System.identityHashCode( system );
086                    logger.trace( "system classloader id: " + id );
087                }
088                EventQueue queue = new EventQueue( logger, "DPML Transit Event Queue" );
089                return new DefaultTransitModel( queue, logger, directive );
090            }
091            catch( Exception e )
092            {
093                final String error = 
094                  "Unexpected error while constructing static secure model.";
095                throw new RuntimeException( error, e );
096            }
097        }
098        
099       /**
100        * Resolve the transit configuration using the default resource path 
101        * <tt>local:xml:dpml/transit/config</tt>. If the resource does not exist a classic 
102        * default scenario will be returned.
103        *
104        * @return the transit model
105        * @exception Exception if an error occurs during model construction
106        */
107        public static DefaultTransitModel getDefaultModel() throws Exception
108        {
109            return getDefaultModel( "transit" );
110        }
111        
112       /**
113        * Resolve the transit configuration using the default resource path 
114        * <tt>local:xml:dpml/transit/config</tt>. If the resource does not exist a classic 
115        * default scenario will be returned.
116        *
117        * @param category the logging channel category name
118        * @return the transit model
119        * @exception Exception if an error occurs during model construction
120        */
121        public static DefaultTransitModel getDefaultModel( String category ) throws Exception
122        {
123            LoggingAdapter adapter = new LoggingAdapter( category );
124            return getDefaultModel( adapter );
125        }
126        
127       /**
128        * Resolve the transit configuration using the default resource path 
129        * <tt>local:xml:dpml/transit/config</tt>. If the resource does not exist a classic 
130        * default scenario will be returned.
131        *
132        * @param logger the logging channel
133        * @return the transit model
134        * @exception Exception if an error occurs during model construction
135        */
136        public static DefaultTransitModel getDefaultModel( Logger logger ) throws Exception
137        {
138            String path = System.getProperty( PROFILE_KEY );
139            if( logger.isTraceEnabled() )
140            {
141                ClassLoader system = ClassLoader.getSystemClassLoader();
142                int id = System.identityHashCode( system );
143                logger.trace( "system classloader id: " + id );
144            }
145            if( null != path )
146            {
147                URI uri = Artifact.createArtifact( path ).toURI();
148                URL url = uri.toURL();
149                TransitBuilder builder = new TransitBuilder( logger );
150                TransitDirective directive =  builder.load( url );
151                return new DefaultTransitModel( logger, directive );
152            }
153            else
154            {
155                File prefs = Transit.DPML_PREFS;
156                File config = new File( prefs, "dpml/transit/xmls/standard.xml" );
157                if( config.exists() )
158                {
159                    URI uri = config.toURI();
160                    URL url = uri.toURL();
161                    TransitBuilder builder = new TransitBuilder( logger );
162                    TransitDirective directive =  builder.load( url );
163                    return new DefaultTransitModel( logger, directive );
164                }
165                else
166                {
167                    return getClassicModel( logger );
168                }
169            }
170        }
171        
172        // ------------------------------------------------------------------------
173        // state
174        // ------------------------------------------------------------------------
175        
176        private final DefaultProxyModel m_proxy;
177        private final DefaultCacheModel m_cache;
178    
179        // ------------------------------------------------------------------------
180        // constructor
181        // ------------------------------------------------------------------------
182    
183       /**
184        * Creation of a new TransitModel using a supplied configuration
185        * and logging channel.  The implementation will construct a proxy
186        * model, layout registry model, cache model, and repository codebase 
187        * model using the supplied configuration.
188        *
189        * @param logger the assigned loging channel
190        * @param directive the transit configuration
191        * @exception NullPointerException if the logger or directive arguments are null
192        * @exception RemoteException if a remote exception occurs
193        */
194        public DefaultTransitModel( Logger logger, TransitDirective directive ) 
195          throws RemoteException, NullPointerException
196        {
197            this( 
198              new EventQueue( logger, "DPML Transit Event Queue" ), 
199              logger, directive );
200        }
201        
202       /**
203        * Creation of a new TransitModel using a supplied configuration
204        * and logging channel.  The implementation will construct a proxy
205        * model, layout registry model, cache model, and repository codebase 
206        * model using the supplied configuration.
207        *
208        * @param queue the event queue 
209        * @param logger the assigned logging channel
210        * @param directive the transit configuration
211        * @exception NullPointerException if the logger or directive arguments are null
212        * @exception RemoteException if a remote exception occurs
213        */
214        public DefaultTransitModel( EventQueue queue, Logger logger, TransitDirective directive ) 
215          throws RemoteException, NullPointerException
216        {
217            super( queue, logger );
218    
219            if( null == directive )
220            {
221                throw new NullPointerException( "directive" );
222            }
223            
224            m_proxy = createProxyModel( queue, directive );
225            m_cache = createCacheModel( queue, directive );
226        }
227    
228        // ------------------------------------------------------------------------
229        // TransitModel
230        // ------------------------------------------------------------------------
231    
232       /**
233        * Return the proxy configuration model.
234        * @return the proxy model (null if no proxy config defined).
235        */
236        public ProxyModel getProxyModel()
237        {
238            return m_proxy;
239        }
240    
241       /**
242        * Return the cache model.
243        * @return the cache model 
244        */
245        public CacheModel getCacheModel()
246        {
247            return m_cache;
248        }
249    
250       /**
251        * Add a disposal listener to the model.
252        * @param listener the listener to add
253        */
254        public void addDisposalListener( DisposalListener listener )
255        {
256            super.addListener( listener );
257        }
258    
259       /**
260        * Remove a disposal listener from the model.
261        * @param listener the listener to remove
262        */
263        public void removeDisposalListener( DisposalListener listener )
264        {
265            super.removeListener( listener );
266        }
267    
268       /**
269        * Internal event handler.
270        * @param eventObject the event to handle
271        */
272        public void processEvent( EventObject eventObject )
273        {
274            if( eventObject instanceof DisposalEvent )
275            {
276                DisposalEvent event = (DisposalEvent) eventObject;
277                processDisposalEvent( event );
278            }
279        }
280        
281        private void processDisposalEvent( DisposalEvent event )
282        {
283            EventListener[] listeners = super.getEventListeners();
284            for( int i=0; i < listeners.length; i++ )
285            {
286                EventListener listener = listeners[i];
287                if( listener instanceof DisposalListener )
288                {
289                    DisposalListener pl = (DisposalListener) listener;
290                    try
291                    {
292                        pl.notifyDisposal( event );
293                    }
294                    catch( Throwable e )
295                    {
296                        final String error =
297                          "Disposal notification error.";
298                        getLogger().error( error, e );
299                    }
300                }
301            }
302        }
303        
304        // ------------------------------------------------------------------------
305        // impl
306        // ------------------------------------------------------------------------
307    
308        Logger getLoggingChannel()
309        {
310            return getLogger();
311        }
312    
313       /**
314        * Trigger disposal of the transit model.
315        */
316        public synchronized void dispose()
317        {
318            DisposalEvent event = new DisposalEvent( this );
319            enqueueEvent( event, true );
320            disposeCacheModel();
321            disposeProxyModel();
322            super.dispose();
323            getEventQueue().terminateDispatchThread();
324            Thread thread = new Terminator( this );
325            thread.start();
326        }
327        
328       /**
329        * Internal model terminator.
330        */
331        private class Terminator extends Thread
332        {
333            private final DefaultTransitModel m_model;
334            Terminator( DefaultTransitModel model )
335            {
336                m_model = model;
337            }
338            
339           /**
340            * Initiate model retraction from the RMI.
341            */
342            public void run()
343            {
344                try
345                {
346                    UnicastRemoteObject.unexportObject( m_model, true );
347                }
348                catch( NoSuchObjectException e )
349                {
350                    // ignore
351                }
352                catch( RemoteException e )
353                {
354                    e.printStackTrace();
355                }
356            }
357        }
358        
359        private synchronized void disposeProxyModel()
360        {
361            if( null == m_proxy )
362            {
363                return;
364            }
365            else
366            {
367                m_proxy.dispose();
368                try
369                {
370                    UnicastRemoteObject.unexportObject( m_proxy, true );
371                }
372                catch( NoSuchObjectException e )
373                {
374                    // ignore
375                }
376                catch( RemoteException e )
377                {
378                    getLogger().warn( "Remote error during proxy reference removal.", e );
379                } 
380            }
381        }
382        
383        private synchronized void disposeCacheModel()
384        {
385            m_cache.dispose();
386            try
387            {
388                UnicastRemoteObject.unexportObject( m_cache, true );
389            }
390            catch( NoSuchObjectException e )
391            {
392                // ignore
393            }
394            catch( RemoteException e )
395            {
396                getLogger().warn( "Remote error during cache reference removal.", e );
397            }
398        }
399        
400        private DefaultProxyModel createProxyModel( final EventQueue queue, final TransitDirective directive )
401        {
402            try
403            {
404                ProxyDirective config = directive.getProxyDirective();
405                if( null == config )
406                {
407                    return null;
408                }
409                else
410                {
411                    Logger logger = getLogger().getChildLogger( "proxy" );
412                    return new DefaultProxyModel( queue, logger, config );
413                }
414            }
415            catch( Throwable e )
416            {
417                final String error = 
418                  "An error occured during construction of the proxy model.";
419                throw new TransitError( error, e );
420            }
421        }
422    
423        private DefaultCacheModel createCacheModel( final EventQueue queue, final TransitDirective directive )
424        {
425            try
426            {
427                Logger logger = getLogger().getChildLogger( "cache" );
428                CacheDirective config = directive.getCacheDirective();
429                return new DefaultCacheModel( queue, logger, config );
430            }
431            catch( Throwable e )
432            {
433                final String error = 
434                  "An error occured during construction of the cache model.";
435                throw new TransitError( error, e );
436            }
437        }
438    
439        static DefaultTransitModel getBootstrapModel() throws Exception
440        {
441            Logger logger = new LoggingAdapter( "transit" );
442            return getSecureModel( logger );
443        }
444        
445        static DefaultTransitModel getClassicModel( Logger logger ) throws Exception
446        {
447            TransitDirective directive = TransitDirective.CLASSIC_PROFILE;
448            EventQueue queue = new EventQueue( logger, "DPML Transit Event Queue" );
449            return new DefaultTransitModel( queue, logger, directive );
450        }
451        
452        private static URI createStaticURI( String path )
453        {
454            try
455            {
456                return Artifact.createArtifact( path ).toURI();
457            }
458            catch( Exception e )
459            {
460                return null;
461            }
462        }
463        
464    }
465