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.0
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        * DPML environment variable string.
108        */
109        private static final String HOME_SYMBOL = "DPML_HOME";
110    
111       /**
112        * DPML environment variable string.
113        */
114        private static final String SYSTEM_SYMBOL = "DPML_SYSTEM";
115    
116       /**
117        * The DPML home directory established via assesment of the the ${dpml.home}
118        * system property and the <tt>DPML_HOME</tt> environment variable.
119        */
120        public static final File HOME;
121    
122       /**
123        * If a system property named "dpml.system" is defined then the value
124        * is assigned otherwise the implementation will look for an environment
125        * variable <tt>DPML_SYSTEM</tt>.
126        */
127        public static final File SYSTEM;
128    
129       /**
130        * The Transit personal data directory. The location of this diectory is system
131        * dependent.
132        */
133        public static final File DATA;
134    
135       /**
136        * The Transit personal preferences directory. The location of this diectory is system
137        * dependent.
138        */
139        public static final File PREFS;
140    
141       /**
142        * The Transit system version.
143        */
144        public static final String VERSION = "2.1.0";
145    
146       /**
147        * Default configuration path.
148        */
149        private static final String STANDARD_PATH = "dpml/transit/xmls/standard.xml";
150    
151       /**
152        * System property key used to hold an overriding configuration url.
153        */
154        private static final String PROFILE_KEY = "dpml.transit.profile";
155    
156        private static final Logger LOGGER = new DefaultLogger( "dpml.transit" );
157    
158        static
159        {
160            String pkgs = System.getProperty( "java.protocol.handler.pkgs" );
161            if( null == pkgs )
162            {
163                System.setProperty( "java.protocol.handler.pkgs", "dpml.transit|net.dpml.transit" );
164            }
165            else
166            {
167                System.setProperty( "java.protocol.handler.pkgs", pkgs + "|dpml.transit|net.dpml.transit" );
168            }
169            
170            System.setProperty( "dpml.transit.version", VERSION );
171            
172            HOME = resolveHomeDirectory();
173            SYSTEM = resolveSystemDirectory( HOME );
174            DATA = resolveDataDirectory( HOME );
175            PREFS = resolvePreferencesDirectory( HOME );
176    
177            System.setProperty( SYSTEM_KEY, SYSTEM.getAbsolutePath() );
178            System.setProperty( SHARE_KEY, SYSTEM.getAbsolutePath() );
179            System.setProperty( HOME_KEY, HOME.getAbsolutePath() );
180            System.setProperty( DATA_KEY, DATA.getAbsolutePath() );
181            System.setProperty( PREFS_KEY, PREFS.getAbsolutePath() );
182        }
183        
184       /**
185        * Returns the singleton instance of the transit system. If Transit
186        * has not been initialized the transit configuration will be resolved 
187        * using the System property <tt>dpml.transit.profile</tt>.
188        * @return the singleton transit instance
189        * @exception TransitError if an error occurs during establishment
190        */
191        public static synchronized Transit getInstance() throws TransitError
192        {
193            if( null == m_INSTANCE )
194            {
195                if( LOGGER.isTraceEnabled() )
196                {
197                    LOGGER.trace( "version " + VERSION );
198                    LOGGER.trace( "codebase: " 
199                      + Transit.class.getProtectionDomain().getCodeSource().getLocation()
200                    );
201                }
202                try
203                {
204                    TransitDirective directive = loadTransitDirective();
205                    m_INSTANCE = new Transit( directive );
206                    return m_INSTANCE;
207                }
208                catch( Throwable e )
209                {
210                    final String error = 
211                      "Transit initialization failure.";
212                    throw new TransitError( error, e );
213                }
214            }
215            else
216            {
217                return m_INSTANCE;
218            }
219        }
220        
221        //------------------------------------------------------------------
222        // state 
223        //------------------------------------------------------------------
224    
225       /**
226        * Internal transit context.
227        */
228        private TransitContext m_context;
229    
230        //------------------------------------------------------------------
231        // constructor 
232        //------------------------------------------------------------------
233    
234       /**
235        * Private constructor of a transit instance.
236        *
237        * @param directive the transit configuration
238        * @exception TransitException if an establishment error occurs
239        */
240        private Transit( TransitDirective directive ) throws TransitException
241        {
242            try
243            {
244                m_context = TransitContext.create( directive );
245            }
246            //catch( TransitException e )
247            //{
248            //    throw e;
249            //}
250            catch( Throwable e )
251            {
252                final String error = "Internal error while attempting to create the Transit context.";
253                throw new TransitException( error, e );
254            }
255        }
256        
257        //------------------------------------------------------------------
258        // implementation 
259        //------------------------------------------------------------------
260    
261       /**
262        * Return the singleton transit content.
263        *
264        * @return the context instance
265        * @exception IllegalStateException if transit has not been initialized
266        */
267        TransitContext getTransitContext() throws IllegalStateException
268        {
269            if( null == m_context )
270            {
271                final String error = 
272                  "Transit context has not been initialized.";
273                throw new IllegalStateException( error );
274            }
275            else
276            {
277                return m_context;
278            }
279        }
280    
281       /**
282        * Return the current cache directory.
283        * @return the cache directory.
284        */
285        public File getCacheDirectory()
286        {
287            return getTransitContext().getCacheHandler().getCacheDirectory();
288        }
289    
290       /**
291        * Return the link manager.
292        * @return the link manager
293        */
294        public LinkManager getLinkManager()
295        {
296            return getTransitContext().getLinkManager();
297        }
298        
299       /**
300        * Return the cache layout.
301        * @return the layout
302        */
303        public Layout getCacheLayout()
304        {
305            return getTransitContext().getCacheLayout();
306        }
307        
308       /**
309        * Add a monitor to Transit.
310        * @param monitor the monitor to add
311        */
312        public void addMonitor( Monitor monitor )
313        {
314            getTransitContext().addMonitor( monitor );
315        }
316        
317       /**
318        * Return the content handler fo the supplied content type.
319        * @param type the content handler type
320        * @return the content handler or null if no content handler found
321        */
322        public ContentHandler getContentHandler( String type )
323        {
324            return getTransitContext().getContentHandler( type );
325        }
326        
327       /**
328        * Resolve the DPML home directory using assesment of the the ${dpml.home}
329        * system property, the HOME environment variable.  If HOME is
330        * not declared, the behaviour is platform specific.  If the os is Windows,
331        * the value returned is equivalent to $APPDATA\DPML whereas Unix environment
332        * will return ${user.home}/.dpml. The value returned may be overriden by 
333        * setting a 'dpml.home' system property.
334        *
335        * @return the DPML home directory
336        */
337        private static File resolveHomeDirectory()
338        {
339            String home = System.getProperty( HOME_KEY );
340            if( null != home )
341            {
342                return new File( home );
343            }
344            home = System.getenv( HOME_SYMBOL );
345            if( null != home )
346            {
347                return new File( home );
348            }
349            String os = System.getProperty( "os.name" ).toLowerCase();
350            if( os.indexOf( "win" ) >= 0 )
351            {
352                home = System.getenv( "APPDATA" );
353                File data = new File( home );
354                return new File( data, "DPML" );
355            }
356            else
357            {
358                File user = new File( System.getProperty( "user.home" ) );
359                return new File( user, ".dpml" );
360            }
361        }
362    
363       /**
364        * Resolve the DPML system home directory.  If a system property
365        * named "dpml.system" is defined then the value as a file is
366        * returned otherwise the implementation will look for an environment
367        * variable named "SYSTEM" which if defined will be
368        * returned as a file otherwise a value equivalent to 
369        * <tt>${dpml.home}/share</tt> will be returned.
370        *
371        * @param dpmlHomeDir the default HOME value
372        * @return the transit system directory
373        */
374        private static File resolveSystemDirectory( File dpmlHomeDir )
375        {
376            String home = System.getProperty( SYSTEM_KEY );
377            if( null != home )
378            {
379                return new File( home );
380            }
381            home = System.getenv( SYSTEM_SYMBOL );
382            if( null != home )
383            {
384                return new File( home );
385            }
386            else
387            {
388                return new File( dpmlHomeDir, "share" );
389            }
390        }
391    
392       /**
393        * Resolve the DPML data directory. The value
394        * returned may be overriden by setting a 'dpml.data' 
395        * system property otherwise the default value returned
396        * will be equivalent to <tt>${dpml.home}/data</tt>.
397        *
398        * @param dir the default HOME value
399        * @return the transit personal data directory
400        */
401        private static File resolveDataDirectory( File dir )
402        {
403            String path = System.getProperty( DATA_KEY );
404            if( null != path )
405            {
406                return new File( path );
407            }
408            else
409            {
410                return new File( dir, "data" );
411            }
412        }
413    
414       /**
415        * Resolve the DPML prefs directory. The value
416        * returned may be overriden by setting a 'dpml.prefs' 
417        * system property otherwise the default value returned
418        * will be equivalent to <tt>${dpml.home}/prefs</tt>.
419        *
420        * @param dir the default HOME value
421        * @return the transit personal data directory
422        */
423        private static File resolvePreferencesDirectory( File dir )
424        {
425            String path = System.getProperty( PREFS_KEY );
426            if( null != path )
427            {
428                return new File( path );
429            }
430            else
431            {
432                return new File( dir, "prefs" );
433            }
434        }
435    
436        //------------------------------------------------------------------
437        // static internal 
438        //------------------------------------------------------------------
439    
440       /**
441        * Singleton transit instance.
442        */
443        private static Transit m_INSTANCE;
444    
445       /**
446        * Resolve the transit configuration using the default resource path 
447        * <tt>local:xml:dpml/transit/config</tt>. If the resource does not exist a classic 
448        * default scenario will be returned.
449        *
450        * @return the transit configuration directive
451        * @exception Exception if an error occurs during model construction
452        */
453        private static TransitDirective loadTransitDirective() throws Exception
454        {
455            String path = System.getProperty( PROFILE_KEY );
456            if( null != path )
457            {
458                URL url = resolveURL( path );
459                return loadTransitDirective( url );
460            }
461            else
462            {
463                File prefs = Transit.PREFS;
464                File config = new File( prefs, STANDARD_PATH );
465                if( config.exists() )
466                {
467                    URI uri = config.toURI();
468                    URL url = uri.toURL();
469                    return loadTransitDirective( url );
470                }
471                else
472                {
473                    return TransitDirective.CLASSIC_PROFILE;
474                }
475            }
476        }
477        
478        private static TransitDirective loadTransitDirective( URL url ) throws Exception
479        {
480            if( LOGGER.isTraceEnabled() )
481            {
482                LOGGER.trace( 
483                  "configuration [" 
484                  + url
485                  + "]" );
486            }
487            return TransitDirective.decode( url );
488        }
489        
490        private static URL resolveURL( String path ) throws Exception
491        {
492            if( path.indexOf( ":" ) > -1 )
493            {
494                // its a url
495                URI uri = new URI( path );
496                return Artifact.toURL( uri );
497            }
498            else
499            {
500                // its a file path
501                File file = new File( path );
502                if( file.exists() )
503                {
504                    return file.toURI().toURL();
505                }
506                else
507                {
508                    File prefs = Transit.PREFS;
509                    File alt = new File( prefs, path );
510                    if( alt.exists() )
511                    {
512                        return alt.toURI().toURL();
513                    }
514                    else
515                    {
516                        throw new FileNotFoundException( path ); 
517                    }
518                }
519            }
520        }
521    }