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 implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package net.dpml.lang; 017 018 import dpml.lang.Info; 019 import dpml.lang.Classpath; 020 import dpml.lang.Part; 021 import dpml.lang.DOM3DocumentBuilder; 022 023 import dpml.util.DefaultLogger; 024 import dpml.util.SimpleResolver; 025 import dpml.util.ElementHelper; 026 027 import java.io.IOException; 028 import java.net.URI; 029 import java.net.URL; 030 import java.net.URLConnection; 031 import java.lang.management.ManagementFactory; 032 import java.util.Map; 033 import java.util.Hashtable; 034 import java.util.ServiceLoader; 035 import java.util.ArrayList; 036 037 import javax.management.MBeanServer; 038 import javax.management.ObjectName; 039 import javax.management.InstanceAlreadyExistsException; 040 041 import net.dpml.annotation.Component; 042 043 import net.dpml.appliance.Appliance; 044 import net.dpml.appliance.ApplianceFactory; 045 046 import net.dpml.runtime.ComponentStrategyHandler; 047 048 import net.dpml.transit.Artifact; 049 import net.dpml.transit.ContentHandler; 050 051 import net.dpml.util.Logger; 052 import net.dpml.util.Resolver; 053 054 import org.w3c.dom.Element; 055 import org.w3c.dom.Document; 056 import org.w3c.dom.TypeInfo; 057 058 /** 059 * Content handler for the 'part' artifact type. 060 * @author <a href="http://www.dpml.net">Digital Product Management Library</a> 061 * @version 2.1.0 062 */ 063 public final class PartContentHandler extends ContentHandler implements PartContentManager, ApplianceFactory 064 { 065 /** 066 * Part XSD uri. 067 */ 068 public static final String NAMESPACE = "dpml:part"; 069 070 private static final String PART_CONTENT_TYPE = "part"; 071 072 private static final Logger LOGGER = new DefaultLogger( "dpml.part" ); 073 074 // DOES NOT WORK 075 //private static final Map<String, WeakReference<Part>> CACHE = 076 // new Hashtable<String, WeakReference<Part>>(); 077 078 // POTENTIAL SOLUTION: map of anchor classloader (keys) to a map of part classloader and parts 079 //private static final Map<ClassLoader,Map<ClassLoader,Part>> CACHE = 080 // new Hashtable<ClassLoader,Map<ClassLoader,Part>>(); 081 082 private static final Map<String,Part> CACHE = new Hashtable<String,Part>(); 083 084 private static final DOM3DocumentBuilder DOCUMENT_BUILDER = 085 new DOM3DocumentBuilder(); 086 087 private static final ComponentStrategyHandler STANDARD_STRATEGY_HANDLER = 088 new ComponentStrategyHandler(); 089 090 /** 091 * Return a strategy handler based on the supplied component annotation. 092 * @param annotation the component annotation 093 * @return the strategy handler 094 * @exception Exception if an error ocurrs during handler establishment 095 */ 096 public static StrategyHandler getStrategyHandler( Component annotation ) throws Exception 097 { 098 String classname = annotation.handlerClassname(); 099 try 100 { 101 return getStrategyHandler( classname ); 102 } 103 catch( UnknownServiceException e ) 104 { 105 String urn = annotation.handlerURI(); 106 URI uri = new URI( urn ); 107 return getStrategyHandler( uri ); 108 } 109 } 110 111 /** 112 * Return the strategy handler supporting the supplied class. If the class 113 * contains the component annotation the handler is resolved relative to the 114 * annotation properties, otherwise a default strategy handler is returned. 115 * 116 * @param subject the subject class 117 * @return the strategy handler 118 * @exception Exception if a general loading error occurs 119 */ 120 public static StrategyHandler getStrategyHandler( Class<?> subject ) throws Exception 121 { 122 if( subject.isAnnotationPresent( Component.class ) ) 123 { 124 Component annotation = 125 subject.getAnnotation( Component.class ); 126 return PartContentHandler.getStrategyHandler( annotation ); 127 } 128 else 129 { 130 return STANDARD_STRATEGY_HANDLER; 131 } 132 } 133 134 /** 135 * Load a potentially foreign strategy handler. 136 * Strategy resolution is based on the following rules relative to the namespace 137 * of the supplied element: 138 * <ul> 139 * <li>if a system property named <tt>handler:[namespace]</tt> is defined, 140 * it is assumed to be a handler classname and the implementation will 141 * load the class and instantiate the handler instance.</li> 142 * <li>if the namespace is recognized as a standard strategy the appropriate 143 * strategy handler is returned</li> 144 * <li>the implementation will evaluate the <tt<handler</tt> attribute on the 145 * supplied element - if the attribute value is not null, then 146 * the values will interprited as either a classname or a uri. If the 147 * value contains the ':' character the value will be interprited as a 148 * uri to a part definition from which a strategy handler can be established, 149 * otherwise, the implementation assumes the the value is a strategy handler 150 * classname resolvable form the current context classloader</li> 151 * </ul> 152 * 153 * @param element the strategy element 154 * @return the strategy handler 155 * @exception Exception if loading error occurs 156 */ 157 public static StrategyHandler getStrategyHandler( Element element ) throws Exception 158 { 159 TypeInfo info = element.getSchemaTypeInfo(); 160 String namespace = info.getTypeNamespace(); 161 String override = System.getProperty( "handler:" + namespace ); 162 if( null != override ) 163 { 164 return getStrategyHandler( override ); 165 } 166 if( AntlibStrategyHandler.NAMESPACE.equals( namespace ) ) 167 { 168 return new AntlibStrategyHandler(); 169 } 170 else if( ComponentStrategyHandler.NAMESPACE.equals( namespace ) ) 171 { 172 return new ComponentStrategyHandler(); 173 } 174 else 175 { 176 String urn = ElementHelper.getAttribute( element, "handler" ); 177 if( null != urn ) 178 { 179 if( urn.indexOf( ":" ) > -1 ) 180 { 181 URI uri = new URI( urn ); 182 return getStrategyHandler( uri ); 183 } 184 else 185 { 186 return getStrategyHandler( urn ); 187 } 188 } 189 else 190 { 191 try 192 { 193 URI uri = getURIFromNamespaceURI( namespace ); 194 return getStrategyHandler( uri ); 195 } 196 catch( Exception e ) 197 { 198 final String error = 199 "Cannot resolve strategy handler for element."; 200 throw new DecodingException( error, element ); 201 } 202 } 203 } 204 } 205 206 /** 207 * Load a strategy handler. The implementation will attempt to resolve a 208 * part defintion from the supplied uri, caching a reference to 209 * the handler, and returning the strategy handler instance. 210 * 211 * @param uri the part handler part uri 212 * @exception Exception if part loading error occurs 213 */ 214 static StrategyHandler getStrategyHandler( URI uri ) throws Exception 215 { 216 ClassLoader context = Thread.currentThread().getContextClassLoader(); 217 Thread.currentThread().setContextClassLoader( ClassLoader.getSystemClassLoader() ); // TODO 218 try 219 { 220 Strategy strategy = Strategy.load( null, null, uri, null ); 221 StrategyHandler handler = strategy.getContentForClass( StrategyHandler.class ); 222 if( null == handler ) 223 { 224 final String error = 225 "URI does not resolve to a strategy handler [" 226 + uri 227 + "]"; 228 throw new ServiceError( error ); 229 } 230 else 231 { 232 return handler; 233 } 234 } 235 finally 236 { 237 Thread.currentThread().setContextClassLoader( context ); 238 } 239 } 240 241 /** 242 * Load a strategy handler. 243 * 244 * @param classname the strategy handler service implementation class 245 * @exception Exception if part loading error occurs 246 */ 247 static StrategyHandler getStrategyHandler( String classname ) throws Exception 248 { 249 ServiceLoader<StrategyHandler> handlers = ServiceLoader.load( StrategyHandler.class ); 250 for( StrategyHandler handler : handlers ) 251 { 252 if( handler.getClass().getName().equals( classname ) ) 253 { 254 return handler; 255 } 256 } 257 throw new UnknownServiceException( classname ); 258 } 259 260 //-------------------------------------------------------------------------------- 261 // constructor 262 //-------------------------------------------------------------------------------- 263 264 /** 265 * Creation of a new part content handler. 266 * @exception Exception if an error occurs 267 */ 268 public PartContentHandler() throws Exception 269 { 270 String flag = System.getProperty( "dpml.jmx.enabled", "false" ); 271 if( "true".equals( flag ) ) 272 { 273 try 274 { 275 MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 276 Hashtable<String,String> table = new Hashtable<String,String>(); 277 table.put( "type", "Parts" ); 278 ObjectName name = 279 ObjectName.getInstance( "net.dpml.transit", table ); 280 server.registerMBean( this, name ); 281 } 282 catch( InstanceAlreadyExistsException e ) 283 { 284 //e.printStackTrace(); 285 } 286 } 287 } 288 289 //-------------------------------------------------------------------------------- 290 // ContentHandler 291 //-------------------------------------------------------------------------------- 292 293 /** 294 * Returns the type tha the content handler supports. 295 * @return the content type name 296 */ 297 public String getType() 298 { 299 return PART_CONTENT_TYPE; 300 } 301 302 /** 303 * Returns the content in the form of a {@link net.dpml.lang.Strategy} datatype. 304 * @param connection the url connection 305 * @return the part datatype 306 * @exception IOException if an IO error occurs 307 */ 308 public Object getContent( URLConnection connection ) throws IOException 309 { 310 Part part = getPartContent( connection ); 311 return part.getStrategy(); 312 } 313 314 /** 315 * Returns the content assignable to the first recognized class in the list 316 * os supppied classes. If the class array is empty the part datatype is returned. 317 * If none of the classes are recognized, null is returned. 318 * @param connection the url connection 319 * @param classes the selection class array 320 * @return the resolved instance 321 * @exception IOException if an IO error occurs 322 */ 323 public Object getContent( URLConnection connection, Class[] classes ) throws IOException 324 { 325 Part part = getPartContent( connection ); 326 return getContentForClasses( part, classes ); 327 } 328 329 //-------------------------------------------------------------------------------- 330 // ApplianceFactory 331 //-------------------------------------------------------------------------------- 332 333 /** 334 * Create a new appliance using the supplied connection object. 335 * @param connection the URL connection 336 * @param partition an optional partition name 337 * @return the appliance 338 * @exception IOException if an IO error occurs 339 */ 340 public Appliance newAppliance( URLConnection connection, String partition ) throws IOException 341 { 342 Part part = getPartContent( connection, partition ); 343 return getContentForClass( part, Appliance.class ); 344 } 345 346 //-------------------------------------------------------------------------------- 347 // ContentManager 348 //-------------------------------------------------------------------------------- 349 350 /** 351 * Return the part managers handled by the content handler. 352 * @return the part manager array 353 */ 354 public PartManager[] getPartManagers() 355 { 356 ArrayList<PartManager> list = new ArrayList<PartManager>(); 357 Part[] parts = CACHE.values().toArray( new Part[0] ); 358 for( Part part : parts ) 359 { 360 if( part instanceof PartManager ) 361 { 362 PartManager manager = (PartManager) part; 363 list.add( manager ); 364 } 365 } 366 return list.toArray( new PartManager[0] ); 367 } 368 369 //-------------------------------------------------------------------------------- 370 // internals 371 //-------------------------------------------------------------------------------- 372 373 private Logger getLogger() 374 { 375 return LOGGER; 376 } 377 378 /** 379 * Return a resolved value given a supplied part instance and class. 380 * @param part the part to resolve against 381 * @param c the return type 382 * @return the resolved value 383 * @exception IOException if an error occurs 384 */ 385 private <T>T getContentForClass( Part part, Class<T> c ) throws IOException 386 { 387 if( Part.class == c ) 388 { 389 return c.cast( part ); 390 } 391 Strategy strategy = part.getStrategy(); 392 return strategy.getContentForClass( c ); 393 } 394 395 private Object getContentForClasses( Part part, Class[] classes ) throws IOException 396 { 397 Strategy strategy = part.getStrategy(); 398 for( Class<?> c : classes ) 399 { 400 Object content = strategy.getContentForClass( c ); 401 if( null != content ) 402 { 403 return content; 404 } 405 } 406 return null; 407 } 408 409 static Part getPartContent( URLConnection connection ) throws IOException 410 { 411 return getPartContent( connection, null, true ); 412 } 413 414 static Part getPartContent( URLConnection connection, String name ) throws IOException 415 { 416 return getPartContent( connection, name, true ); 417 } 418 419 static Part getPartContent( URLConnection connection, String name, boolean cache ) throws IOException 420 { 421 return getPartContent( null, connection, name, cache ); 422 } 423 424 static Part getPartContent( ClassLoader anchor, URLConnection connection, String name, boolean cache ) throws IOException 425 { 426 ClassLoader classloader = getAnchorClassLoader( anchor ); 427 URL url = connection.getURL(); 428 try 429 { 430 String key = buildKey( classloader, url ); 431 if( cache ) 432 { 433 //WeakReference ref = CACHE.get( key ); 434 //if( null != ref ) 435 //{ 436 // Part part = (Part) ref.get(); 437 //if( null != part ) 438 //{ 439 // if( getLogger().isTraceEnabled() ) 440 // { 441 // getLogger().trace( "located cached part: " + url ); 442 // } 443 // return part; 444 //} 445 //else 446 //{ 447 // if( getLogger().isTraceEnabled() ) 448 // { 449 // getLogger().trace( "located disgarded ref: " + key ); 450 // } 451 //} 452 //} 453 454 Part part = CACHE.get( key ); 455 if( null != part ) 456 { 457 return part; 458 } 459 else 460 { 461 // otherwise we need to build it 462 463 part = buildPart( classloader, connection, name, true ); 464 //if( part instanceof PartManager ) 465 //{ 466 // // register it with the mbean server 467 //} 468 469 //WeakReference<Part> reference = new WeakReference<Part>( part ); 470 //CACHE.put( key, reference ); 471 CACHE.put( key, part ); 472 if( LOGGER.isTraceEnabled() ) 473 { 474 LOGGER.trace( "caching part" 475 + "\n url: " + url 476 + "\n key: " + key ); 477 } 478 return part; 479 } 480 } 481 else 482 { 483 if( LOGGER.isTraceEnabled() ) 484 { 485 LOGGER.trace( "building new part: " + url ); 486 } 487 return buildPart( classloader, connection, name, true ); 488 } 489 } 490 catch( IOException e ) 491 { 492 throw e; 493 } 494 catch( NoClassDefFoundError e ) 495 { 496 LOGGER.error( 497 e.toString() 498 + "\n" 499 + classloader.toString() ); 500 throw e; 501 } 502 catch( Exception e ) 503 { 504 final String error = "Unexpected error in part handling: " + url; 505 IOException ioe = new IOException(); 506 ioe.initCause( e ); 507 throw ioe; 508 } 509 } 510 511 private static Part buildPart( 512 ClassLoader anchor, URLConnection connection, String name, boolean validate ) throws Exception 513 { 514 URL url = connection.getURL(); 515 if( LOGGER.isTraceEnabled() ) 516 { 517 LOGGER.trace( 518 "building part [" 519 + url 520 + "] with anchor [" 521 + System.identityHashCode( anchor ) 522 + "]" ); 523 } 524 525 Document document = DOCUMENT_BUILDER.parse( connection ); 526 final Element element = document.getDocumentElement(); 527 TypeInfo type = element.getSchemaTypeInfo(); 528 String namespace = type.getTypeNamespace(); 529 if( isNamespaceRecognized( namespace ) ) 530 { 531 URI uri = url.toURI(); 532 Resolver resolver = new SimpleResolver(); 533 Info info = getInfo( uri, element ); 534 Classpath classpath = getClasspath( element ); 535 ClassLoader classloader = ClassLoaderHelper.newClassLoader( anchor, uri, classpath ); 536 ClassLoader context = Thread.currentThread().getContextClassLoader(); 537 Thread.currentThread().setContextClassLoader( classloader ); 538 try 539 { 540 Element elem = getStrategyElement( element ); 541 StrategyHandler handler = getStrategyHandler( elem ); 542 String query = url.getQuery(); 543 Strategy strategy = handler.build( classloader, elem, resolver, name, query, validate ); 544 if( LOGGER.isTraceEnabled() ) 545 { 546 LOGGER.trace( 547 "establised new part using [" 548 + strategy.getClass().getName() 549 + "]" ); 550 } 551 return new Part( info, classpath, strategy ); 552 } 553 finally 554 { 555 Thread.currentThread().setContextClassLoader( context ); 556 } 557 } 558 else 559 { 560 final String error = 561 "Part namespace not recognized." 562 + "\nFound: " + namespace 563 + "\nExpecting: " + PartContentHandler.NAMESPACE; 564 throw new DecodingException( error, element ); 565 } 566 } 567 568 private static boolean isNamespaceRecognized( String namespace ) 569 { 570 return NAMESPACE.equals( namespace ); 571 } 572 573 private static Info getInfo( URI uri, Element root ) 574 { 575 Element element = ElementHelper.getChild( root, "info" ); 576 if( null == element ) 577 { 578 return new Info( uri, null, null ); 579 } 580 String title = ElementHelper.getAttribute( element, "title" ); 581 Element descriptionElement = ElementHelper.getChild( element, "description" ); 582 String description = ElementHelper.getValue( descriptionElement ); 583 return new Info( uri, title, description ); 584 } 585 586 /** 587 * Construct the classpath defintion. 588 * @param root the element containing a 'classpath' element. 589 * @return the classpath definition 590 * @exception DecodingException if an error occurs during element evaluation 591 */ 592 private static Classpath getClasspath( Element root ) throws DecodingException 593 { 594 // TODO: update to support different classpath defintions (e.g. 277 module scenario) 595 596 Element classpath = ElementHelper.getChild( root, "classpath" ); 597 if( null == classpath ) 598 { 599 return new Classpath(); 600 } 601 602 try 603 { 604 Element[] children = ElementHelper.getChildren( classpath ); 605 URI[] sys = buildURIs( classpath, "system" ); 606 URI[] pub = buildURIs( classpath, "public" ); 607 URI[] prot = buildURIs( classpath, "protected" ); 608 URI[] priv = buildURIs( classpath, "private" ); 609 return new Classpath( sys, pub, prot, priv ); 610 } 611 catch( Throwable e ) 612 { 613 final String error = 614 "Unable to decode classpath due to an unexpected error."; 615 throw new DecodingException( error, e, classpath ); 616 } 617 } 618 619 private static URI[] buildURIs( Element classpath, String key ) throws Exception 620 { 621 Element category = ElementHelper.getChild( classpath, key ); 622 Element[] children = ElementHelper.getChildren( category, "uri" ); 623 URI[] uris = new URI[ children.length ]; 624 for( int i=0; i<children.length; i++ ) 625 { 626 Element child = children[i]; 627 String value = ElementHelper.getValue( child ); 628 uris[i] = new URI( value ); 629 } 630 return uris; 631 } 632 633 // cache utils 634 635 private static String buildKey( ClassLoader classloader, URL url ) throws IOException 636 { 637 try 638 { 639 int n = System.identityHashCode( classloader ); 640 return "" + n + "#" + url.toURI().toASCIIString(); 641 } 642 catch( Exception e ) 643 { 644 final String error = "Internal error while resolving part key from url: " + url; 645 IOException ioe = new IOException(); 646 ioe.initCause( e ); 647 throw ioe; 648 } 649 } 650 651 static ClassLoader getAnchorClassLoader( ClassLoader parent ) 652 { 653 if( null != parent ) 654 { 655 return parent; 656 } 657 else 658 { 659 return Strategy.class.getClassLoader(); 660 //ClassLoader classloader = Thread.currentThread().getContextClassLoader(); 661 //if( null == classloader ) 662 //{ 663 // return getClass().getClassLoader(); 664 //} 665 //else 666 //{ 667 // return classloader; 668 //} 669 } 670 } 671 672 private static Element getStrategyElement( Element root ) throws DecodingException 673 { 674 // TODO: update this to select the strategy element based on type overwise 675 // we risk issues when dealing with a non-standard classpath element 676 677 Element[] children = ElementHelper.getChildren( root ); 678 for( Element element : children ) 679 { 680 String name = element.getLocalName(); 681 if( !name.equals( "info" ) && !name.equals( "classpath" ) ) 682 { 683 return element; 684 } 685 } 686 final String error = 687 "Missing part strategy element."; 688 throw new DecodingException( error, root ); 689 } 690 691 /** 692 * Resolve the part handler given an element namespace. 693 * @param urn the namespace value 694 * @return the decoder uri 695 * @exception Exception if an error occurs 696 */ 697 private static URI getURIFromNamespaceURI( String urn ) throws Exception 698 { 699 URI raw = new URI( urn ); 700 Artifact artifact = Artifact.createArtifact( raw ); 701 String group = artifact.getGroup(); 702 String name = artifact.getName(); 703 String path = "link:part:" + group + "/" + name; 704 Artifact link = Artifact.createArtifact( path ); 705 return link.toURI(); 706 } 707 }