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 }