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