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 Laboratory</a> 073 * @version 2.0.2 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.0.2"; 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 }