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