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 }