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 }