001 /* 002 * Copyright 2006 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.lang; 020 021 import java.io.File; 022 import java.io.IOException; 023 import java.net.URI; 024 import java.net.URL; 025 import java.util.Map; 026 import java.util.Hashtable; 027 import java.lang.ref.WeakReference; 028 029 import net.dpml.transit.link.ArtifactLinkManager; 030 031 import net.dpml.util.Logger; 032 import net.dpml.util.DefaultLogger; 033 import net.dpml.util.ElementHelper; 034 import net.dpml.util.DOM3DocumentBuilder; 035 import net.dpml.util.Decoder; 036 import net.dpml.util.DecoderFactory; 037 import net.dpml.util.DecodingException; 038 import net.dpml.util.Resolver; 039 import net.dpml.util.SimpleResolver; 040 041 import org.w3c.dom.Document; 042 import org.w3c.dom.Element; 043 import org.w3c.dom.TypeInfo; 044 045 /** 046 * Construct a part. 047 */ 048 public final class PartDecoder implements Decoder 049 { 050 /** 051 * Part XSD uri. 052 */ 053 public static final String PART_XSD_URI = "link:xsd:dpml/lang/dpml-part#1.0"; 054 055 private static final DOM3DocumentBuilder DOCUMENT_BUILDER = 056 new DOM3DocumentBuilder(); 057 058 private static final ValueDecoder VALUE_DECODER = new ValueDecoder(); 059 060 private static final PartDecoder DECODER = new PartDecoder(); 061 062 private static final String BASEPATH = setupBasePathSpec(); 063 064 /** 065 * Get the singleton instance of the part decoder. 066 * @return the decoder instance. 067 */ 068 public static PartDecoder getInstance() 069 { 070 return DECODER; 071 } 072 073 private Map m_map = new Hashtable(); 074 private Map m_builders = new Hashtable(); 075 076 private Logger m_logger; 077 078 private PartDecoder() 079 { 080 m_logger = new DefaultLogger( "dpml.lang" ); 081 } 082 083 /** 084 * Load a part from a uri. 085 * @param uri the part uri 086 * @param cache if true parts are cached relative to the requested uri 087 * @return the part definition 088 * @exception IOException if an IO error occurs 089 */ 090 public Part loadPart( URI uri, boolean cache ) throws IOException 091 { 092 if( null == uri ) 093 { 094 throw new NullPointerException( "uri" ); 095 } 096 if( getLogger().isDebugEnabled() ) 097 { 098 String path = getPartSpec( uri ); 099 if( getLogger().isTraceEnabled() ) 100 { 101 if( cache ) 102 { 103 getLogger().trace( "loading part (cache enabled): " + path ); 104 } 105 else 106 { 107 getLogger().trace( "loading part (cache disabled): " + path ); 108 } 109 } 110 else 111 { 112 getLogger().debug( "loading part: " + path ); 113 } 114 } 115 String key = buildKey( uri ); 116 if( cache ) 117 { 118 WeakReference ref = (WeakReference) m_map.get( key ); 119 if( null != ref ) 120 { 121 Part part = (Part) ref.get(); 122 if( null != part ) 123 { 124 if( getLogger().isDebugEnabled() ) 125 { 126 getLogger().debug( "located part in cache" ); 127 } 128 return part; 129 } 130 } 131 } 132 133 // cache based retrieval was disabled or no cache value present 134 135 try 136 { 137 final Document document = DOCUMENT_BUILDER.parse( uri ); 138 final Element root = document.getDocumentElement(); 139 Resolver resolver = new SimpleResolver(); 140 Part value = decodePart( uri, root, resolver ); 141 if( cache ) 142 { 143 WeakReference reference = new WeakReference( value ); 144 m_map.put( key, reference ); 145 if( getLogger().isTraceEnabled() ) 146 { 147 getLogger().trace( "caching part" 148 + "\n uri: " + uri 149 + "\n key: " + key ); 150 } 151 } 152 return value; 153 } 154 catch( Throwable e ) 155 { 156 final String error = 157 "An error while attempting to load a part." 158 + "\n uri: " + uri; 159 IOException exception = new IOException( error ); 160 exception.initCause( e ); 161 throw exception; 162 } 163 } 164 165 private String buildKey( URI uri ) throws IOException 166 { 167 ClassLoader classloader = getAnchorClassLoader(); 168 int n = System.identityHashCode( classloader ); 169 return "" + n + "#" + getRealURI( uri ).toASCIIString(); 170 } 171 172 private String getID() 173 { 174 ClassLoader classloader = getAnchorClassLoader(); 175 int n = System.identityHashCode( classloader ); 176 return "" + n; 177 } 178 179 private URI getRealURI( URI uri ) throws IOException 180 { 181 if( "link".equals( uri.getScheme() ) ) 182 { 183 ArtifactLinkManager manager = new ArtifactLinkManager(); 184 return manager.getTargetURI( uri ); 185 } 186 else 187 { 188 return uri; 189 } 190 } 191 192 /** 193 * Resolve a object from a DOM element. 194 * @param element the dom element 195 * @param resolver build-time value resolver 196 * @return the resolved object 197 * @exception IOException if an error occurs during element evaluation 198 */ 199 public Object decode( Element element, Resolver resolver ) throws IOException 200 { 201 return decodePart( null, element, resolver ); 202 } 203 204 /** 205 * Resolve a part from a DOM element. 206 * @param uri the part uri 207 * @param element element part definition 208 * @param resolver build-time value resolver 209 * @return the resolved part datastructure 210 * @exception IOException if an error occurs during element evaluation 211 */ 212 public Part decodePart( URI uri, Element element, Resolver resolver ) throws IOException 213 { 214 TypeInfo info = element.getSchemaTypeInfo(); 215 String namespace = info.getTypeNamespace(); 216 if( PART_XSD_URI.equals( namespace ) ) 217 { 218 boolean alias = ElementHelper.getBooleanAttribute( element, "alias", false ); 219 Info information = getInfo( uri, element ); 220 Classpath classpath = getClasspath( element ); 221 Element strategy = getStrategyElement( element ); 222 return build( m_logger, information, classpath, strategy, resolver ); 223 } 224 else 225 { 226 final String error = 227 "Part namespace not recognized." 228 + "\nExpecting: " + PART_XSD_URI 229 + "\nFound: " + namespace; 230 throw new DecodingException( element, error ); 231 } 232 } 233 234 /** 235 * Resolve a part plugin or resource strategy. 236 * @param logger the logging channel 237 * @param information the part info definition 238 * @param classpath the part classpath definition 239 * @param strategy part deployment strategy definition 240 * @param resolver build-time value resolver 241 * @return the resolved part 242 * @exception IOException if an error occurs during element evaluation 243 */ 244 public Part build( 245 Logger logger, Info information, Classpath classpath, Element strategy, Resolver resolver ) 246 throws IOException 247 { 248 ClassLoader anchor = getAnchorClassLoader(); 249 TypeInfo info = strategy.getSchemaTypeInfo(); 250 String namespace = info.getTypeNamespace(); 251 if( PART_XSD_URI.equals( namespace ) ) 252 { 253 // this is either a plugin or a resource 254 255 String name = info.getTypeName(); 256 if( "plugin".equals( name ) ) 257 { 258 if( logger.isTraceEnabled() ) 259 { 260 logger.trace( "reading plugin definition" ); 261 } 262 String classname = ElementHelper.getAttribute( strategy, "class" ); 263 Element[] elements = ElementHelper.getChildren( strategy ); 264 Value[] values = VALUE_DECODER.decodeValues( elements ); 265 Part part = new Plugin( logger, information, classpath, classname, values ); 266 if( logger.isTraceEnabled() ) 267 { 268 logger.trace( "loaded plugin definition" ); 269 } 270 return part; 271 } 272 else if( "resource".equals( name ) ) 273 { 274 if( logger.isTraceEnabled() ) 275 { 276 logger.trace( "reading resource definition" ); 277 } 278 String urn = ElementHelper.getAttribute( strategy, "urn" ); 279 String path = ElementHelper.getAttribute( strategy, "path" ); 280 Part part = new Resource( logger, information, classpath, urn, path ); 281 if( logger.isTraceEnabled() ) 282 { 283 logger.trace( "loaded resource definition" ); 284 } 285 return part; 286 } 287 else 288 { 289 final String error = 290 "Element type name [" 291 + name 292 + "] is not a recognized element type within the " 293 + PART_XSD_URI 294 + " namespace."; 295 throw new DecodingException( strategy, error ); 296 } 297 } 298 else 299 { 300 // this is a foreign part 301 302 try 303 { 304 URI uri = getDecoderURI( strategy ); 305 Builder builder = loadForeignBuilder( uri ); 306 if( logger.isTraceEnabled() ) 307 { 308 logger.trace( 309 "using builder [" 310 + builder.getClass().getName() 311 + "]" ); 312 } 313 Part part = builder.build( logger, information, classpath, strategy, resolver ); 314 if( logger.isTraceEnabled() ) 315 { 316 logger.trace( 317 "loaded part [" 318 + part.getClass().getName() 319 + "]" ); 320 } 321 return part; 322 } 323 catch( Exception ioe ) 324 { 325 final String error = 326 "Internal error while attempting to load foreign part."; 327 throw new DecodingException( strategy, error, ioe ); 328 } 329 finally 330 { 331 Thread.currentThread().setContextClassLoader( anchor ); 332 } 333 } 334 } 335 336 /** 337 * Resolve the element decoder uri. 338 * 339 * @param element the DOM element 340 * @return the decoder uri 341 * @exception DecodingException if an error occurs 342 */ 343 public URI getDecoderURI( Element element ) throws DecodingException 344 { 345 String uri = ElementHelper.getAttribute( element, "handler" ); 346 if( null != uri ) 347 { 348 try 349 { 350 return new URI( uri ); 351 } 352 catch( Exception e ) 353 { 354 final String error = 355 "Internal error while resolving handler attribute (expecting uri value)"; 356 throw new DecodingException( element, error, e ); 357 } 358 } 359 TypeInfo info = element.getSchemaTypeInfo(); 360 String namespace = info.getTypeNamespace(); 361 try 362 { 363 return DecoderFactory.getDecoderURIFromNamespaceURI( namespace ); 364 } 365 catch( Exception e ) 366 { 367 final String error = 368 "Internal error while attempting to resolve default decoder uri."; 369 throw new DecodingException( element, error, e ); 370 } 371 } 372 373 /** 374 * Get the assigned logging channel. 375 * @return the logging channel 376 */ 377 protected Logger getLogger() 378 { 379 return m_logger; 380 } 381 382 /** 383 * Load a forign part builder. The implementation will attempt to resolve a 384 * plugin defintion from the supplied uri, caching a reference to 385 * the builder, and returning the plugin instance as a builder instance. 386 * 387 * @param uri the part builder uri 388 * @exception DecodingException if a part decoding error occurs 389 * @exception Exception if part loading error occurs 390 */ 391 private Builder loadForeignBuilder( URI uri ) throws DecodingException, Exception 392 { 393 WeakReference ref = (WeakReference) m_builders.get( uri ); 394 if( null != ref ) 395 { 396 Builder builder = (Builder) ref.get(); 397 if( null != builder ) 398 { 399 if( getLogger().isTraceEnabled() ) 400 { 401 getLogger().trace( "located builder [" + uri + "]" ); 402 } 403 return builder; 404 } 405 else 406 { 407 if( getLogger().isTraceEnabled() ) 408 { 409 getLogger().trace( "reloading builder [" + uri + "]" ); 410 } 411 } 412 } 413 else 414 { 415 if( getLogger().isTraceEnabled() ) 416 { 417 getLogger().trace( "loading builder [" + uri + "]" ); 418 } 419 } 420 421 Part part = loadPart( uri, true ); 422 Logger logger = getLogger(); 423 Object[] args = new Object[]{logger}; 424 Object object = part.instantiate( args ); 425 if( Builder.class.isAssignableFrom( object.getClass() ) ) 426 { 427 Builder builder = (Builder) object; 428 WeakReference reference = new WeakReference( builder ); 429 m_builders.put( uri, reference ); 430 return builder; 431 } 432 else 433 { 434 String stack = 435 StandardClassLoader.toString( 436 getClass().getClassLoader(), 437 object.getClass().getClassLoader() ); 438 String classname = object.getClass().getName(); 439 final String error = 440 "Plugin instance is not assignable to the " 441 + Builder.class.getName() 442 + " interface." 443 + "\n URI: " + uri 444 + "\n Class: " + classname 445 + "\n" + stack; 446 throw new PartException( error ); 447 } 448 } 449 450 private Element getStrategyElement( Element root ) throws DecodingException 451 { 452 Element[] children = ElementHelper.getChildren( root ); 453 if( children.length != 3 ) 454 { 455 final String error = 456 "Illegal number of child elements in <part>. Expecting 3, found " 457 + children.length 458 + "."; 459 throw new DecodingException( root, error ); 460 } 461 return children[1]; 462 } 463 464 465 private Info getInfo( URI uri, Element root ) 466 { 467 Element element = ElementHelper.getChild( root, "info" ); 468 String title = ElementHelper.getAttribute( element, "title" ); 469 Element descriptionElement = ElementHelper.getChild( element, "description" ); 470 String description = ElementHelper.getValue( descriptionElement ); 471 return new Info( uri, title, description ); 472 } 473 474 /** 475 * Construct the classpath defintion. 476 * @param root the element containing a 'classpath' element. 477 * @return the classpath definition 478 * @exception DecodingException if an error occurs during element evaluation 479 */ 480 protected Classpath getClasspath( Element root ) throws DecodingException 481 { 482 Element classpath = ElementHelper.getChild( root, "classpath" ); 483 if( null == classpath ) 484 { 485 final String error = 486 "Required classpath element is not present in plugin descriptor."; 487 throw new DecodingException( root, error ); 488 } 489 490 try 491 { 492 Element[] children = ElementHelper.getChildren( classpath ); 493 URI[] sys = buildURIs( classpath, "system" ); 494 URI[] pub = buildURIs( classpath, "public" ); 495 URI[] prot = buildURIs( classpath, "protected" ); 496 URI[] priv = buildURIs( classpath, "private" ); 497 Classpath cp = new Classpath( sys, pub, prot, priv ); 498 return cp; 499 } 500 catch( Throwable e ) 501 { 502 final String error = 503 "Unable to decode classpath due to an unexpected error."; 504 throw new DecodingException( classpath, error, e ); 505 } 506 } 507 508 private URI[] buildURIs( Element classpath, String key ) throws Exception 509 { 510 Element category = ElementHelper.getChild( classpath, key ); 511 if( null == category ) 512 { 513 return new URI[0]; 514 } 515 else 516 { 517 Element[] children = ElementHelper.getChildren( category, "uri" ); 518 URI[] uris = new URI[ children.length ]; 519 for( int i=0; i<children.length; i++ ) 520 { 521 Element child = children[i]; 522 String value = ElementHelper.getValue( child ); 523 uris[i] = new URI( value ); 524 } 525 return uris; 526 } 527 } 528 529 private ClassLoader getAnchorClassLoader() 530 { 531 ClassLoader classloader = Thread.currentThread().getContextClassLoader(); 532 if( null == classloader ) 533 { 534 return Part.class.getClassLoader(); 535 } 536 else 537 { 538 return classloader; 539 } 540 } 541 542 private static String setupBasePathSpec() 543 { 544 try 545 { 546 String path = System.getProperty( "user.dir" ); 547 File file = new File( path ); 548 URI uri = file.toURI(); 549 URL url = uri.toURL(); 550 return url.toString(); 551 } 552 catch( Exception e ) 553 { 554 return e.toString(); 555 } 556 } 557 558 static String getPartSpec( URI uri ) 559 { 560 String path = uri.toASCIIString(); 561 if( path.startsWith( BASEPATH ) ) 562 { 563 return "./" + path.substring( BASEPATH.length() ); 564 } 565 else 566 { 567 return path; 568 } 569 } 570 }