001    /*
002     * Copyright 2004-2005 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 java.io.File;
023    import java.io.FileOutputStream;
024    import java.io.IOException;
025    import java.io.OutputStreamWriter;
026    import java.io.PrintWriter;
027    
028    import net.dpml.transit.link.LinkManager;
029    import net.dpml.transit.monitor.LoggingAdapter;
030    import net.dpml.transit.monitor.RepositoryMonitorRouter;
031    import net.dpml.transit.monitor.CacheMonitorRouter;
032    import net.dpml.transit.monitor.NetworkMonitorRouter;
033    import net.dpml.transit.model.TransitModel;
034    
035    import net.dpml.lang.UnknownKeyException;
036    import net.dpml.util.Logger;
037    
038    /**
039     * The Transit class manages the establishment of a singleton transit instance
040     * together with a service supporting the deployment of a application plugin and
041     * access to transit monitor routers.
042     *
043     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
044     * @version 1.0.3
045     */
046    public final class Transit
047    {
048        //------------------------------------------------------------------
049        // static 
050        //------------------------------------------------------------------
051    
052       /**
053        * DPML home key.
054        */
055        public static final String HOME_KEY = "dpml.home";
056    
057       /**
058        * DPML data key.
059        */
060        public static final String DATA_KEY = "dpml.data";
061    
062       /**
063        * DPML prefs key.
064        */
065        public static final String PREFS_KEY = "dpml.prefs";
066    
067       /**
068        * Transit system key.
069        */
070        public static final String SYSTEM_KEY = "dpml.system";
071    
072       /**
073        * Transit share key (alias to dpml.system).
074        */
075        public static final String SHARE_KEY = "dpml.share";
076    
077       /**
078        * DPML environment variable string.
079        */
080        public static final String HOME_SYMBOL = "DPML_HOME";
081    
082       /**
083        * DPML environment variable string.
084        */
085        public static final String SYSTEM_SYMBOL = "DPML_SYSTEM";
086    
087       /**
088        * The DPML home directory established via assesment of the the ${dpml.home}
089        * system property and the DPML_HOME environment variable.
090        */
091        public static final File DPML_HOME;
092    
093       /**
094        * If a system property named "dpml.system" is defined then the value
095        * is assigned otherwise the implementation will look for an environment
096        * variable named "DPML_SYSTEM".
097        */
098        public static final File DPML_SYSTEM;
099    
100       /**
101        * The Transit personal data directory. The location of this diectory is system
102        * dependent.
103        */
104        public static final File DPML_DATA;
105    
106       /**
107        * The Transit personal preferences directory. The location of this diectory is system
108        * dependent.
109        */
110        public static final File DPML_PREFS;
111    
112       /**
113        * The Transit system version.
114        */
115        public static final String VERSION = "1.0.3";
116    
117        static
118        {
119            System.setProperty( "java.protocol.handler.pkgs", "net.dpml.transit" );
120            System.setProperty( "dpml.transit.version", VERSION );
121    
122            DPML_HOME = resolveHomeDirectory();
123            DPML_SYSTEM = resolveSystemDirectory( DPML_HOME );
124            DPML_DATA = resolveDataDirectory( DPML_HOME );
125            DPML_PREFS = resolvePreferencesDirectory( DPML_HOME );
126    
127            System.setProperty( SYSTEM_KEY, DPML_SYSTEM.getAbsolutePath() );
128            System.setProperty( SHARE_KEY, DPML_SYSTEM.getAbsolutePath() );
129            System.setProperty( HOME_KEY, DPML_HOME.getAbsolutePath() );
130            System.setProperty( DATA_KEY, DPML_DATA.getAbsolutePath() );
131            System.setProperty( PREFS_KEY, DPML_PREFS.getAbsolutePath() );
132        }
133    
134       /**
135        * Returns the singleton instance of the transit system. If Transit
136        * has not been initialized a the transit model will be resolved 
137        * using the System property <tt>dpml.transit.profile</tt>.
138        * @return the singleton transit instance
139        * @exception TransitError if an error occurs during establishment
140        * @see DefaultTransitModel#getDefaultModel
141        */
142        public static Transit getInstance() throws TransitError
143        {
144            synchronized( Transit.class )
145            {
146                if( m_INSTANCE == null )
147                {
148                    try
149                    {
150                        Logger logger = new LoggingAdapter( "dpml.transit" );
151                        TransitModel model = DefaultTransitModel.getDefaultModel( logger );
152                        return getInstance( model );
153                    }
154                    catch( Throwable e )
155                    {
156                        String message = e.getMessage();
157                        Throwable cause = e.getCause();
158                        throw new TransitError( message, cause );
159                    }
160                }
161                else
162                {
163                    return m_INSTANCE;
164                }
165            }
166        }
167        
168       /**
169        * Returns the singleton instance of the transit system.  If this method
170        * has already been invoked the server and monitor argument will be ignored.
171        *
172        * @param model the activate transit model
173        * @return the singleton transit instance
174        * @exception IOException if an error occurs during establishment
175        * @exception TransitAlreadyInitializedException if Transit is already initialized
176        */
177        public static Transit getInstance( TransitModel model )
178            throws IOException, TransitAlreadyInitializedException
179        {
180            synchronized( Transit.class )
181            {
182                if( m_INSTANCE == null )
183                {
184                    m_INSTANCE = new Transit( model );
185    
186                    // before returning from this method we need to give the transit
187                    // subsystems a chance to complete initialization actions that 
188                    // are themselves dependent on an establish Transit instance
189    
190                    m_INSTANCE.getTransitContext().initialize();
191                    return m_INSTANCE;
192                }
193                else
194                {
195                    final String error = 
196                      "Transit has already been initialized.";
197                    throw new TransitAlreadyInitializedException( error );
198                }
199            }
200        }
201        
202        //------------------------------------------------------------------
203        // state 
204        //------------------------------------------------------------------
205    
206       /**
207        * Singleton repository monitor router.
208        */
209        private RepositoryMonitorRouter m_repositoryMonitor;
210    
211       /**
212        * Singleton cache monitor router.
213        */
214        private CacheMonitorRouter m_cacheMonitor;
215    
216       /**
217        * Singleton network monitor router.
218        */
219        private NetworkMonitorRouter m_networkMonitor;
220    
221       /**
222        * PrintWriter where operations troubleshooting messages
223        * can be written to.
224        */
225        private PrintWriter m_logWriter;
226    
227        private SecuredTransitContext m_context;
228    
229       /**
230        * Return the singleton transit content.
231        * @return the context instance
232        * @exception IllegalStateException if transit has not been initialized
233        */
234        SecuredTransitContext getTransitContext() throws IllegalStateException
235        {
236            if( null == m_context )
237            {
238                final String error = 
239                  "Transit context has not been initialized.";
240                throw new IllegalStateException( error );
241            }
242            else
243            {
244                return m_context;
245            }
246        }
247    
248       /**
249        * Private constructor of a transit instance.
250        * @param model the active transit model
251        * @exception TransitException if an establishment error occurs
252        */
253        private Transit( TransitModel model ) throws TransitException
254        {
255            //
256            // create the transit context
257            //
258    
259            try
260            {
261                m_context = SecuredTransitContext.create( model );
262            }
263            catch( TransitException e )
264            {
265                throw e;
266            }
267            catch( Throwable e )
268            {
269                final String error = "Unable to construct transit context.";
270                throw new TransitException( error, e );
271            }
272    
273            //
274            // setup the monitors
275            //
276    
277            m_repositoryMonitor = new RepositoryMonitorRouter();
278            m_cacheMonitor = new CacheMonitorRouter();
279            m_networkMonitor = new NetworkMonitorRouter();
280    
281            try
282            {
283                // Setting up a temporary directory for Transit.
284    
285                File temp = new File( DPML_DATA, "temp" );
286                temp.mkdirs();
287    
288                // Setting up a permanent output troubleshooting resource
289                // for Transit.
290                File logs = new File( DPML_DATA, "logs" );
291                File logDir = new File( logs, "transit" );
292                logDir.mkdirs();
293                File logFile = new File( logDir, "transit.log" );
294                FileOutputStream fos = new FileOutputStream( logFile );
295                OutputStreamWriter osw = new OutputStreamWriter( fos, "UTF-8" );
296                m_logWriter = new PrintWriter( osw, true );
297            }
298            catch( Throwable e )
299            {
300                final String error = "Unable to construct transit instance.";
301                throw new TransitException( error, e );
302            }
303        }
304        
305       /**
306        * Return the current cache directory.
307        * @return the cache directory.
308        */
309        public File getCacheDirectory()
310        {
311            return getTransitContext().getCacheHandler().getCacheDirectory();
312        }
313    
314       /**
315        * Return the link manager.
316        * @return the link manager
317        */
318        public LinkManager getLinkManager()
319        {
320            return getTransitContext().getLinkManager();
321        }
322        
323       /**
324        * Return the cache layout.
325        * @return the layout
326        */
327        public Layout getCacheLayout()
328        {
329            return getTransitContext().getCacheLayout();
330        }
331        
332       /**
333        * Return a layout object matching the supplied identifier.
334        * @param id the layout identifier
335        * @return the layout object
336        * @exception UnknownKeyException if the supplied layout id is unknown
337        * @exception IOException if an IO error occurs
338        */
339        public Layout getLayout( String id ) throws UnknownKeyException, IOException
340        {
341            return getTransitContext().getLayout( id );
342        }
343        
344       /**
345        * Return the Transit repository service.
346        * @return the repository service
347        * @exception IllegalStateException if Transit has not been initialized
348        */
349        //public Repository getRepository() throws IllegalStateException 
350        //{
351        //    return getTransitContext().getRepository();
352        //}
353    
354       /**
355        * Returns a reference to the repository monitor router.  Client application
356        * may use the router to add, remove or replace existing monitors.
357        * @return the repository monitor router
358        */
359        public RepositoryMonitorRouter getRepositoryMonitorRouter()
360        {
361            return m_repositoryMonitor;
362        }
363    
364       /**
365        * Returns a reference to the cache monitor router.  Client application
366        * may use the router to add, remove or replace existing monitors.
367        * @return the cache monitor router
368        */
369        public CacheMonitorRouter getCacheMonitorRouter()
370        {
371            return m_cacheMonitor;
372        }
373    
374       /**
375        * Returns a reference to the netowork monitor router.  Client application
376        * may use the router to add, remove or replace existing monitors.
377        * @return the network monitor router
378        */
379        public NetworkMonitorRouter getNetworkMonitorRouter()
380        {
381            return m_networkMonitor;
382        }
383    
384       /** Returns the LogWriter for the Transit system.
385        * This writer should only be used to report information that
386        * should not be output to the user in the course of normal
387        * execution but can aid to determine what has gone wrong in
388        * Transit, such as configuration problems, network problems
389        * and security issues.
390        * @return a PrintWriter where troubleshooting information can
391        * be written to.
392        */
393        public PrintWriter getLogWriter()
394        {
395            return m_logWriter;
396        }
397    
398       /**
399        * Resolve the DPML home directory using assesment of the the ${dpml.home}
400        * system property, the DPML_HOME environment variable.  If DPML_HOME is
401        * not declared, the behaviour is platform specific.  If the os is Windows,
402        * the value returned is equivalent to $APPDATA\DPML whereas Unix environment
403        * will return ${user.home}/.dpml. The value returned may be overriden by 
404        * setting a 'dpml.home' system property.
405        *
406        * @return the DPML home directory
407        */
408        private static File resolveHomeDirectory()
409        {
410            String home = System.getProperty( HOME_KEY );
411            if( null != home )
412            {
413                return new File( home );
414            }
415            home = Environment.getEnvVariable( HOME_SYMBOL );
416            if( null != home )
417            {
418                return new File( home );
419            }
420            String os = System.getProperty( "os.name" ).toLowerCase();
421            if( os.indexOf( "win" ) >= 0 )
422            {
423                home = Environment.getEnvVariable( "APPDATA" );
424                File data = new File( home );
425                return new File( data, "DPML" );
426            }
427            else
428            {
429                File user = new File( System.getProperty( "user.home" ) );
430                return new File( user, ".dpml" );
431            }
432        }
433    
434       /**
435        * Resolve the DPML system home directory.  If a system property
436        * named "dpml.system" is defined then the value as a file is
437        * returned otherwise the implementation will look for an environment
438        * variable named "DPML_SYSTEM" which if defined will be
439        * returned as a file otherwise a value equivalent to 
440        * <tt>${dpml.home}/share</tt> will be returned.
441        *
442        * @param dpmlHomeDir the default DPML_HOME value
443        * @return the transit system directory
444        */
445        private static File resolveSystemDirectory( File dpmlHomeDir )
446        {
447            String home = System.getProperty( SYSTEM_KEY );
448            if( null != home )
449            {
450                return new File( home );
451            }
452            home = Environment.getEnvVariable( SYSTEM_SYMBOL );
453            if( null != home )
454            {
455                return new File( home );
456            }
457            else
458            {
459                return new File( dpmlHomeDir, "share" );
460            }
461        }
462    
463       /**
464        * Resolve the DPML data directory. The value
465        * returned may be overriden by setting a 'dpml.data' 
466        * system property otherwise the default value returned
467        * will be equivalent to <tt>${dpml.home}/data</tt>.
468        *
469        * @param dir the default DPML_HOME value
470        * @return the transit personal data directory
471        */
472        private static File resolveDataDirectory( File dir )
473        {
474            String path = System.getProperty( DATA_KEY );
475            if( null != path )
476            {
477                return new File( path );
478            }
479            else
480            {
481                return new File( dir, "data" );
482            }
483        }
484    
485       /**
486        * Resolve the DPML prefs directory. The value
487        * returned may be overriden by setting a 'dpml.prefs' 
488        * system property otherwise the default value returned
489        * will be equivalent to <tt>${dpml.home}/prefs</tt>.
490        *
491        * @param dir the default DPML_HOME value
492        * @return the transit personal data directory
493        */
494        private static File resolvePreferencesDirectory( File dir )
495        {
496            String path = System.getProperty( PREFS_KEY );
497            if( null != path )
498            {
499                return new File( path );
500            }
501            else
502            {
503                return new File( dir, "prefs" );
504            }
505        }
506    
507        //------------------------------------------------------------------
508        // static internal 
509        //------------------------------------------------------------------
510    
511       /**
512        * Singleton transit instance.
513        */
514        private static Transit m_INSTANCE;
515    
516    }