001    /*
002     * Copyright 2004-2007 Stephen McConnell.
003     * Copyright 2004-2005 Niclas Hedhman.
004     *
005     * Licensed  under the  Apache License,  Version 2.0  (the "License");
006     * you may not use  this file  except in  compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *   http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed  under the  License is distributed on an "AS IS" BASIS,
013     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
014     * implied.
015     *
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    package net.dpml.transit;
021    
022    import dpml.util.DefaultLogger;
023    
024    import dpml.transit.TransitContext;
025    import dpml.transit.info.TransitDirective;
026    
027    import java.io.File;
028    import java.io.FileNotFoundException;
029    import java.net.URI;
030    import java.net.URL;
031    
032    import net.dpml.util.Logger;
033    
034    /**
035     * The Transit class manages the establishment of a singleton transit instance.
036     * The implementation establishes an internal cache management system, a suite
037     * of protocol handlers, and a dynamic content handler service.
038     * 
039     * During initialization Transit will load an XML configuration descibing the 
040     * available remote hosts.  The XML file will be resolved using the following 
041     * strategy:
042     *
043     * <ul>
044     *  <li>if the system property <tt>dpml.transit.profile</tt> is defined then
045     *     <ul>
046     *       <li>if the value contains the ':' character the value will be 
047     *           treated as a URL referencing a remote configuration</li>
048     *       <li>otherwise the value will be treated as a relative file path
049     *           that is first evaluated relative to the current working directory 
050     *           and a file exists at that location it will be loaded, otherwise, 
051     *           the path will be evaluated relative to the DPML Preferences root
052     *           directory</li>
053     *     </ul>
054     *   </li>
055     *   <li>otherwise, the default configuration path 
056     *     <tt>dpml/transit/xmls/standard.xml</tt> will be resolved relative to the 
057     *     Preferences root directory</li>
058     *   <li>if no default configuration is found, Transit will assign a standard
059     *     profile</li>
060     * </ul>
061     * 
062     * During initialization Transit will create the following system properties:
063     *
064     * <ul>
065     *  <li><tt>dpml.home</tt> home directory</li>
066     *  <li><tt>dpml.data</tt> data directory</li>
067     *  <li><tt>dpml.prefs</tt> preferences repository root directory</li>
068     *  <li><tt>dpml.share</tt> shared system root directory</li>
069     *  <li><tt>dpml.transit.version</tt> Transit system version</li>
070     * </ul>
071     *
072     * @author <a href="http://www.dpml.net">Digital Product Management Library</a>
073     * @version 2.1.1
074     */
075    public final class Transit
076    {
077        //------------------------------------------------------------------
078        // static 
079        //------------------------------------------------------------------
080    
081       /**
082        * DPML home key.
083        */
084        private static final String HOME_KEY = "dpml.home";
085    
086       /**
087        * DPML data key.
088        */
089        private static final String DATA_KEY = "dpml.data";
090    
091       /**
092        * DPML prefs key.
093        */
094        private static final String PREFS_KEY = "dpml.prefs";
095    
096       /**
097        * Transit system key.
098        */
099        private static final String SYSTEM_KEY = "dpml.system";
100    
101       /**
102        * Transit share key (alias to dpml.system).
103        */
104        private static final String SHARE_KEY = "dpml.share";
105    
106       /**
107        * Transit config key.
108        */
109        private static final String CONFIG_KEY = "dpml.config";
110    
111       /**
112        * DPML environment variable string.
113        */
114        private static final String HOME_SYMBOL = "DPML_HOME";
115    
116       /**
117        * DPML environment variable string.
118        */
119        private static final String SYSTEM_SYMBOL = "DPML_SYSTEM";
120    
121       /**
122        * The DPML home directory established via assesment of the the ${dpml.home}
123        * system property and the <tt>DPML_HOME</tt> environment variable.
124        */
125        public static final File HOME;
126    
127       /**
128        * If a system property named "dpml.system" is defined then the value
129        * is assigned otherwise the implementation will look for an environment
130        * variable <tt>DPML_SYSTEM</tt>.
131        */
132        public static final File SYSTEM;
133    
134       /**
135        * The Transit personal data directory. The location of this diectory is system
136        * dependent.
137        */
138        public static final File DATA;
139    
140       /**
141        * The Transit personal preferences directory. The location of this diectory is system
142        * dependent.
143        */
144        public static final File PREFS;
145    
146       /**
147        * The Transit shared config directory. The location of this diectory is system
148        * dependent.
149        */
150        public static final File CONFIG;
151    
152       /**
153        * The Transit system version.
154        */
155        public static final String VERSION = "2.1.1";
156    
157       /**
158        * Default configuration path.
159        */
160        private static final String STANDARD_PATH = "dpml/transit/xmls/standard.xml";
161    
162       /**
163        * System property key used to hold an overriding configuration url.
164        */
165        private static final String PROFILE_KEY = "dpml.transit.profile";
166    
167        private static final Logger LOGGER = new DefaultLogger( "dpml.transit" );
168    
169        static
170        {
171            String pkgs = System.getProperty( "java.protocol.handler.pkgs" );
172            if( null == pkgs )
173            {
174                System.setProperty( "java.protocol.handler.pkgs", "dpml.transit|net.dpml.transit" );
175            }
176            else
177            {
178                System.setProperty( "java.protocol.handler.pkgs", pkgs + "|dpml.transit|net.dpml.transit" );
179            }
180            
181            System.setProperty( "dpml.transit.version", VERSION );
182            
183            HOME = resolveHomeDirectory();
184            SYSTEM = resolveSystemDirectory( HOME );
185            DATA = resolveDataDirectory( HOME );
186            PREFS = resolvePreferencesDirectory( HOME );
187            CONFIG = resolveConfigDirectory( SYSTEM );
188    
189            System.setProperty( SYSTEM_KEY, SYSTEM.getAbsolutePath() );
190            System.setProperty( SHARE_KEY, SYSTEM.getAbsolutePath() );
191            System.setProperty( HOME_KEY, HOME.getAbsolutePath() );
192            System.setProperty( DATA_KEY, DATA.getAbsolutePath() );
193            System.setProperty( PREFS_KEY, PREFS.getAbsolutePath() );
194            System.setProperty( CONFIG_KEY, CONFIG.getAbsolutePath() );
195        }
196        
197       /**
198        * Returns the singleton instance of the transit system. If Transit
199        * has not been initialized the transit configuration will be resolved 
200        * using the System property <tt>dpml.transit.profile</tt>.
201        * @return the singleton transit instance
202        * @exception TransitError if an error occurs during establishment
203        */
204        public static synchronized Transit getInstance() throws TransitError
205        {
206            if( null == m_INSTANCE )
207            {
208                if( LOGGER.isTraceEnabled() )
209                {
210                    LOGGER.trace( "version " + VERSION );
211                    LOGGER.trace( "codebase: " 
212                      + Transit.class.getProtectionDomain().getCodeSource().getLocation()
213                    );
214                }
215                try
216                {
217                    TransitDirective directive = loadTransitDirective();
218                    m_INSTANCE = new Transit( directive );
219                    return m_INSTANCE;
220                }
221                catch( Throwable e )
222                {
223                    final String error = 
224                      "Transit initialization failure.";
225                    throw new TransitError( error, e );
226                }
227            }
228            else
229            {
230                return m_INSTANCE;
231            }
232        }
233        
234        //------------------------------------------------------------------
235        // state 
236        //------------------------------------------------------------------
237    
238       /**
239        * Internal transit context.
240        */
241        private TransitContext m_context;
242    
243        //------------------------------------------------------------------
244        // constructor 
245        //------------------------------------------------------------------
246    
247       /**
248        * Private constructor of a transit instance.
249        *
250        * @param directive the transit configuration
251        * @exception TransitException if an establishment error occurs
252        */
253        private Transit( TransitDirective directive ) throws TransitException
254        {
255            try
256            {
257                m_context = TransitContext.create( directive );
258            }
259            //catch( TransitException e )
260            //{
261            //    throw e;
262            //}
263            catch( Throwable e )
264            {
265                final String error = "Internal error while attempting to create the Transit context.";
266                throw new TransitException( error, e );
267            }
268        }
269        
270        //------------------------------------------------------------------
271        // implementation 
272        //------------------------------------------------------------------
273    
274       /**
275        * Return the singleton transit content.
276        *
277        * @return the context instance
278        * @exception IllegalStateException if transit has not been initialized
279        */
280        TransitContext getTransitContext() throws IllegalStateException
281        {
282            if( null == m_context )
283            {
284                final String error = 
285                  "Transit context has not been initialized.";
286                throw new IllegalStateException( error );
287            }
288            else
289            {
290                return m_context;
291            }
292        }
293    
294       /**
295        * Return the current cache directory.
296        * @return the cache directory.
297        */
298        public File getCacheDirectory()
299        {
300            return getTransitContext().getCacheHandler().getCacheDirectory();
301        }
302    
303       /**
304        * Return the link manager.
305        * @return the link manager
306        */
307        public LinkManager getLinkManager()
308        {
309            return getTransitContext().getLinkManager();
310        }
311        
312       /**
313        * Return the cache layout.
314        * @return the layout
315        */
316        public Layout getCacheLayout()
317        {
318            return getTransitContext().getCacheLayout();
319        }
320        
321       /**
322        * Add a monitor to Transit.
323        * @param monitor the monitor to add
324        */
325        public void addMonitor( Monitor monitor )
326        {
327            getTransitContext().addMonitor( monitor );
328        }
329        
330       /**
331        * Return the content handler fo the supplied content type.
332        * @param type the content handler type
333        * @return the content handler or null if no content handler found
334        */
335        public ContentHandler getContentHandler( String type )
336        {
337            return getTransitContext().getContentHandler( type );
338        }
339        
340       /**
341        * Resolve the DPML home directory using assesment of the the ${dpml.home}
342        * system property, the HOME environment variable.  If HOME is
343        * not declared, the behaviour is platform specific.  If the os is Windows,
344        * the value returned is equivalent to $APPDATA\DPML whereas Unix environment
345        * will return ${user.home}/.dpml. The value returned may be overriden by 
346        * setting a 'dpml.home' system property.
347        *
348        * @return the DPML home directory
349        */
350        private static File resolveHomeDirectory()
351        {
352            String home = System.getProperty( HOME_KEY );
353            if( null != home )
354            {
355                return new File( home );
356            }
357            home = System.getenv( HOME_SYMBOL );
358            if( null != home )
359            {
360                return new File( home );
361            }
362            String os = System.getProperty( "os.name" ).toLowerCase();
363            if( os.indexOf( "win" ) >= 0 )
364            {
365                home = System.getenv( "APPDATA" );
366                File data = new File( home );
367                return new File( data, "DPML" );
368            }
369            else
370            {
371                File user = new File( System.getProperty( "user.home" ) );
372                return new File( user, ".dpml" );
373            }
374        }
375    
376       /**
377        * Resolve the DPML system home directory.  If a system property
378        * named "dpml.system" is defined then the value as a file is
379        * returned otherwise the implementation will look for an environment
380        * variable named "SYSTEM" which if defined will be
381        * returned as a file otherwise a value equivalent to 
382        * <tt>${dpml.home}/share</tt> will be returned.
383        *
384        * @param dpmlHomeDir the default HOME value
385        * @return the transit system directory
386        */
387        private static File resolveSystemDirectory( File dpmlHomeDir )
388        {
389            String home = System.getProperty( SYSTEM_KEY );
390            if( null != home )
391            {
392                return new File( home );
393            }
394            home = System.getenv( SYSTEM_SYMBOL );
395            if( null != home )
396            {
397                return new File( home );
398            }
399            else
400            {
401                return new File( dpmlHomeDir, "share" );
402            }
403        }
404    
405       /**
406        * Resolve the DPML data directory. The value
407        * returned may be overriden by setting a 'dpml.data' 
408        * system property otherwise the default value returned
409        * will be equivalent to <tt>${dpml.home}/data</tt>.
410        *
411        * @param dir the default HOME value
412        * @return the transit personal data directory
413        */
414        private static File resolveDataDirectory( File dir )
415        {
416            String path = System.getProperty( DATA_KEY );
417            if( null != path )
418            {
419                return new File( path );
420            }
421            else
422            {
423                return new File( dir, "data" );
424            }
425        }
426    
427       /**
428        * Resolve the DPML prefs directory. The value
429        * returned may be overriden by setting a 'dpml.prefs' 
430        * system property otherwise the default value returned
431        * will be equivalent to <tt>${dpml.home}/prefs</tt>.
432        *
433        * @param dir the default HOME value
434        * @return the transit personal data directory
435        */
436        private static File resolvePreferencesDirectory( File dir )
437        {
438            String path = System.getProperty( PREFS_KEY );
439            if( null != path )
440            {
441                return new File( path );
442            }
443            else
444            {
445                return new File( dir, "prefs" );
446            }
447        }
448    
449       /**
450        * Resolve the DPML config directory. The value
451        * returned may be overriden by setting a 'dpml.config' 
452        * system property otherwise the default value returned
453        * will be equivalent to <tt>${dpml.system}/config</tt>.
454        *
455        * @param dir the default system directory
456        * @return the transit shared configuration directory
457        */
458        private static File resolveConfigDirectory( File dir )
459        {
460            String path = System.getProperty( CONFIG_KEY );
461            if( null != path )
462            {
463                return new File( path );
464            }
465            else
466            {
467                return new File( dir, "config" );
468            }
469        }
470    
471        //------------------------------------------------------------------
472        // static internal 
473        //------------------------------------------------------------------
474    
475       /**
476        * Singleton transit instance.
477        */
478        private static Transit m_INSTANCE;
479    
480       /**
481        * Resolve the transit configuration using the default resource path 
482        * <tt>configuration:xml:dpml/transit/config</tt>. If the resource does not exist a classic 
483        * default scenario will be returned.
484        *
485        * @return the transit configuration directive
486        * @exception Exception if an error occurs during model construction
487        */
488        private static TransitDirective loadTransitDirective() throws Exception
489        {
490            String path = System.getProperty( PROFILE_KEY );
491            if( null != path )
492            {
493                URL url = resolveURL( path );
494                return loadTransitDirective( url );
495            }
496            else
497            {
498                File config = Transit.CONFIG;
499                File configuration = new File( config, STANDARD_PATH );
500                if( configuration.exists() )
501                {
502                    URI uri = configuration.toURI();
503                    URL url = uri.toURL();
504                    return loadTransitDirective( url );
505                }
506                else
507                {
508                    return TransitDirective.CLASSIC_PROFILE;
509                }
510            }
511        }
512        
513        private static TransitDirective loadTransitDirective( URL url ) throws Exception
514        {
515            if( LOGGER.isTraceEnabled() )
516            {
517                LOGGER.trace( 
518                  "configuration [" 
519                  + url
520                  + "]" );
521            }
522            return TransitDirective.decode( url );
523        }
524        
525        private static URL resolveURL( String path ) throws Exception
526        {
527            if( path.indexOf( ":" ) > -1 )
528            {
529                // its a url
530                URI uri = new URI( path );
531                return Artifact.toURL( uri );
532            }
533            else
534            {
535                // its a file path
536                File file = new File( path );
537                if( file.exists() )
538                {
539                    return file.toURI().toURL();
540                }
541                else
542                {
543                    File config = Transit.CONFIG;
544                    File alt = new File( config, path );
545                    if( alt.exists() )
546                    {
547                        return alt.toURI().toURL();
548                    }
549                    else
550                    {
551                        throw new FileNotFoundException( path ); 
552                    }
553                }
554            }
555        }
556    }