001 /* 002 * Copyright (c) 2009 The JOMC Project 003 * Copyright (c) 2005 Christian Schulte <cs@jomc.org> 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or without 007 * modification, are permitted provided that the following conditions 008 * are met: 009 * 010 * o Redistributions of source code must retain the above copyright 011 * notice, this list of conditions and the following disclaimer. 012 * 013 * o Redistributions in binary form must reproduce the above copyright 014 * notice, this list of conditions and the following disclaimer in 015 * the documentation and/or other materials provided with the 016 * distribution. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS" 019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR 022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 027 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 028 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 * 030 * $Id: DefaultModelManager.java 891 2009-11-02 03:40:00Z schulte2005 $ 031 * 032 */ 033 package org.jomc.model; 034 035 import java.io.IOException; 036 import java.io.InputStream; 037 import java.io.Reader; 038 import java.net.URI; 039 import java.net.URISyntaxException; 040 import java.net.URL; 041 import java.text.MessageFormat; 042 import java.util.ArrayList; 043 import java.util.Enumeration; 044 import java.util.HashSet; 045 import java.util.Iterator; 046 import java.util.LinkedList; 047 import java.util.List; 048 import java.util.Locale; 049 import java.util.Map; 050 import java.util.ResourceBundle; 051 import java.util.Set; 052 import java.util.jar.Attributes; 053 import java.util.jar.Manifest; 054 import java.util.logging.Level; 055 import javax.xml.XMLConstants; 056 import javax.xml.bind.JAXBContext; 057 import javax.xml.bind.JAXBElement; 058 import javax.xml.bind.JAXBException; 059 import javax.xml.bind.Marshaller; 060 import javax.xml.bind.Unmarshaller; 061 import javax.xml.transform.ErrorListener; 062 import javax.xml.transform.Source; 063 import javax.xml.transform.Transformer; 064 import javax.xml.transform.TransformerConfigurationException; 065 import javax.xml.transform.TransformerException; 066 import javax.xml.transform.TransformerFactory; 067 import javax.xml.transform.URIResolver; 068 import javax.xml.transform.sax.SAXSource; 069 import javax.xml.transform.stream.StreamSource; 070 import javax.xml.validation.SchemaFactory; 071 import org.jomc.model.bootstrap.Schema; 072 import org.jomc.model.bootstrap.Schemas; 073 import org.w3c.dom.ls.LSInput; 074 import org.w3c.dom.ls.LSResourceResolver; 075 import org.xml.sax.EntityResolver; 076 import org.xml.sax.InputSource; 077 import org.xml.sax.SAXException; 078 079 /** 080 * Default {@code ModelManager} implementation. 081 * 082 * <p><b>Schema management</b><ul> 083 * <li>{@link #getBootstrapSchema() }</li> 084 * <li>{@link #getBootstrapContext() }</li> 085 * </ul></p> 086 * 087 * <p><b>Resource management</b><ul> 088 * <li>{@link #getClasspathModules(java.lang.ClassLoader, java.lang.String) }</li> 089 * <li>{@link #getClasspathSchemas(java.lang.ClassLoader, java.lang.String) }</li> 090 * <li>{@link #getClasspathTransformers(java.lang.ClassLoader, java.lang.String) }</li> 091 * </ul></p> 092 * 093 * <p><b>Log management</b><ul> 094 * <li>{@link #getLogLevel() }</li> 095 * <li>{@link #getListeners() }</li> 096 * <li>{@link #log(java.util.logging.Level, java.lang.String, java.lang.Throwable) }</li> 097 * </ul></p> 098 * 099 * @author <a href="mailto:cs@jomc.org">Christian Schulte</a> 100 * @version $Id: DefaultModelManager.java 891 2009-11-02 03:40:00Z schulte2005 $ 101 */ 102 public class DefaultModelManager implements ModelManager 103 { 104 // SECTION-START[ModelManager] 105 106 public EntityResolver getEntityResolver( final ClassLoader classLoader ) 107 { 108 if ( classLoader == null ) 109 { 110 throw new NullPointerException( "classLoader" ); 111 } 112 113 return new EntityResolver() 114 { 115 116 public InputSource resolveEntity( final String publicId, final String systemId ) 117 throws SAXException, IOException 118 { 119 if ( systemId == null ) 120 { 121 throw new NullPointerException( "systemId" ); 122 } 123 124 InputSource schemaSource = null; 125 126 try 127 { 128 Schema s = null; 129 final Schemas classpathSchemas = getClasspathSchemas( classLoader, getDefaultSchemaLocation() ); 130 131 if ( publicId != null ) 132 { 133 s = classpathSchemas.getSchemaByPublicId( publicId ); 134 } 135 if ( s == null ) 136 { 137 s = classpathSchemas.getSchemaBySystemId( systemId ); 138 } 139 140 if ( s != null ) 141 { 142 schemaSource = new InputSource(); 143 schemaSource.setPublicId( s.getPublicId() != null ? s.getPublicId() : publicId ); 144 schemaSource.setSystemId( s.getSystemId() ); 145 146 if ( s.getClasspathId() != null ) 147 { 148 final URL resource = classLoader.getResource( s.getClasspathId() ); 149 150 if ( resource != null ) 151 { 152 schemaSource.setSystemId( resource.toExternalForm() ); 153 } 154 else 155 { 156 if ( isLoggable( Level.WARNING ) ) 157 { 158 log( Level.WARNING, getMessage( "resourceNotFound", new Object[] 159 { 160 s.getClasspathId() 161 } ), null ); 162 163 } 164 } 165 } 166 167 if ( isLoggable( Level.FINE ) ) 168 { 169 log( Level.FINE, getMessage( "resolutionInfo", new Object[] 170 { 171 publicId + ":" + systemId, 172 schemaSource.getPublicId() + ":" + schemaSource.getSystemId() 173 } ), null ); 174 175 } 176 } 177 178 if ( schemaSource == null ) 179 { 180 final URI systemUri = new URI( systemId ); 181 String schemaName = systemUri.getPath(); 182 if ( schemaName != null ) 183 { 184 final int lastIndexOfSlash = schemaName.lastIndexOf( '/' ); 185 if ( lastIndexOfSlash != -1 && lastIndexOfSlash < schemaName.length() ) 186 { 187 schemaName = schemaName.substring( lastIndexOfSlash + 1 ); 188 } 189 190 for ( URL url : getSchemaResources( classLoader ) ) 191 { 192 if ( url.getPath().endsWith( schemaName ) ) 193 { 194 schemaSource = new InputSource(); 195 schemaSource.setPublicId( publicId ); 196 schemaSource.setSystemId( url.toExternalForm() ); 197 198 if ( isLoggable( Level.FINE ) ) 199 { 200 log( Level.FINE, getMessage( "resolutionInfo", new Object[] 201 { 202 systemUri.toASCIIString(), 203 schemaSource.getSystemId() 204 } ), null ); 205 206 } 207 208 break; 209 } 210 } 211 } 212 else 213 { 214 if ( isLoggable( Level.WARNING ) ) 215 { 216 log( Level.WARNING, getMessage( "unsupportedSystemIdUri", new Object[] 217 { 218 systemId, systemUri.toASCIIString() 219 } ), null ); 220 221 } 222 223 schemaSource = null; 224 } 225 } 226 } 227 catch ( final URISyntaxException e ) 228 { 229 if ( isLoggable( Level.WARNING ) ) 230 { 231 log( Level.WARNING, getMessage( "unsupportedSystemIdUri", new Object[] 232 { 233 systemId, e.getMessage() 234 } ), null ); 235 236 } 237 238 schemaSource = null; 239 } 240 catch ( final JAXBException e ) 241 { 242 throw (IOException) new IOException( e.getMessage() ).initCause( e ); 243 } 244 245 return schemaSource; 246 } 247 248 }; 249 } 250 251 public LSResourceResolver getResourceResolver( final ClassLoader classLoader ) 252 { 253 if ( classLoader == null ) 254 { 255 throw new NullPointerException( "classLoader" ); 256 } 257 258 return new LSResourceResolver() 259 { 260 261 public LSInput resolveResource( final String type, final String namespaceURI, final String publicId, 262 final String systemId, final String baseURI ) 263 { 264 if ( XMLConstants.W3C_XML_SCHEMA_NS_URI.equals( type ) ) 265 { 266 try 267 { 268 final InputSource schemaSource = getEntityResolver( classLoader ).resolveEntity( 269 namespaceURI == null ? publicId : namespaceURI, systemId == null ? "" : systemId ); 270 271 if ( schemaSource != null ) 272 { 273 return new LSInput() 274 { 275 276 public Reader getCharacterStream() 277 { 278 return schemaSource.getCharacterStream(); 279 } 280 281 public void setCharacterStream( final Reader characterStream ) 282 { 283 throw new UnsupportedOperationException(); 284 } 285 286 public InputStream getByteStream() 287 { 288 return schemaSource.getByteStream(); 289 } 290 291 public void setByteStream( final InputStream byteStream ) 292 { 293 throw new UnsupportedOperationException(); 294 } 295 296 public String getStringData() 297 { 298 return null; 299 } 300 301 public void setStringData( final String stringData ) 302 { 303 throw new UnsupportedOperationException(); 304 } 305 306 public String getSystemId() 307 { 308 return schemaSource.getSystemId(); 309 } 310 311 public void setSystemId( final String systemId ) 312 { 313 throw new UnsupportedOperationException(); 314 } 315 316 public String getPublicId() 317 { 318 return schemaSource.getPublicId(); 319 } 320 321 public void setPublicId( final String publicId ) 322 { 323 throw new UnsupportedOperationException(); 324 } 325 326 public String getBaseURI() 327 { 328 return baseURI; 329 } 330 331 public void setBaseURI( final String baseURI ) 332 { 333 throw new UnsupportedOperationException(); 334 } 335 336 public String getEncoding() 337 { 338 return schemaSource.getEncoding(); 339 } 340 341 public void setEncoding( final String encoding ) 342 { 343 throw new UnsupportedOperationException(); 344 } 345 346 public boolean getCertifiedText() 347 { 348 return false; 349 } 350 351 public void setCertifiedText( final boolean certifiedText ) 352 { 353 throw new UnsupportedOperationException(); 354 } 355 356 }; 357 } 358 359 } 360 catch ( final SAXException e ) 361 { 362 if ( isLoggable( Level.WARNING ) ) 363 { 364 log( Level.WARNING, getMessage( "unsupportedSystemIdUri", new Object[] 365 { 366 systemId, e.getMessage() 367 } ), null ); 368 369 } 370 } 371 catch ( final IOException e ) 372 { 373 if ( isLoggable( Level.WARNING ) ) 374 { 375 log( Level.WARNING, getMessage( "unsupportedSystemIdUri", new Object[] 376 { 377 systemId, e.getMessage() 378 } ), null ); 379 380 } 381 } 382 } 383 else if ( isLoggable( Level.WARNING ) ) 384 { 385 log( Level.WARNING, getMessage( "unsupportedResourceType", new Object[] 386 { 387 type 388 } ), null ); 389 390 } 391 392 return null; 393 } 394 395 }; 396 } 397 398 public javax.xml.validation.Schema getSchema( final ClassLoader classLoader ) 399 throws IOException, SAXException, JAXBException 400 { 401 if ( classLoader == null ) 402 { 403 throw new NullPointerException( "classLoader" ); 404 } 405 406 final SchemaFactory f = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ); 407 final Schemas schemas = this.getClasspathSchemas( classLoader, getDefaultSchemaLocation() ); 408 final List<Source> sources = new ArrayList<Source>( schemas.getSchema().size() ); 409 final EntityResolver entityResolver = this.getEntityResolver( classLoader ); 410 411 for ( Schema s : schemas.getSchema() ) 412 { 413 final InputSource inputSource = entityResolver.resolveEntity( s.getPublicId(), s.getSystemId() ); 414 415 if ( inputSource != null ) 416 { 417 sources.add( new SAXSource( inputSource ) ); 418 } 419 } 420 421 return f.newSchema( sources.toArray( new Source[ sources.size() ] ) ); 422 } 423 424 public JAXBContext getContext( final ClassLoader classLoader ) throws IOException, SAXException, JAXBException 425 { 426 if ( classLoader == null ) 427 { 428 throw new NullPointerException( "classLoader" ); 429 } 430 431 final StringBuilder packageNames = new StringBuilder(); 432 433 for ( final Iterator<Schema> s = this.getClasspathSchemas( classLoader, getDefaultSchemaLocation() ). 434 getSchema().iterator(); s.hasNext(); ) 435 { 436 final Schema schema = s.next(); 437 if ( schema.getContextId() != null ) 438 { 439 packageNames.append( ':' ).append( schema.getContextId() ); 440 if ( this.isLoggable( Level.FINE ) ) 441 { 442 this.log( Level.FINE, this.getMessage( "addingContext", new Object[] 443 { 444 schema.getContextId() 445 } ), null ); 446 447 } 448 } 449 } 450 451 if ( packageNames.length() == 0 ) 452 { 453 throw new IOException( this.getMessage( "missingSchemas", new Object[] 454 { 455 getDefaultSchemaLocation() 456 } ) ); 457 458 } 459 460 return JAXBContext.newInstance( packageNames.toString().substring( 1 ), classLoader ); 461 } 462 463 public Marshaller getMarshaller( final ClassLoader classLoader ) throws IOException, SAXException, JAXBException 464 { 465 if ( classLoader == null ) 466 { 467 throw new NullPointerException( "classLoader" ); 468 } 469 470 final StringBuilder packageNames = new StringBuilder(); 471 final StringBuilder schemaLocation = new StringBuilder(); 472 473 for ( final Iterator<Schema> s = this.getClasspathSchemas( classLoader, getDefaultSchemaLocation() ). 474 getSchema().iterator(); s.hasNext(); ) 475 { 476 final Schema schema = s.next(); 477 if ( schema.getContextId() != null ) 478 { 479 packageNames.append( ':' ).append( schema.getContextId() ); 480 } 481 if ( schema.getPublicId() != null && schema.getSystemId() != null ) 482 { 483 schemaLocation.append( ' ' ).append( schema.getPublicId() ).append( ' ' ). 484 append( schema.getSystemId() ); 485 486 } 487 } 488 489 if ( packageNames.length() == 0 ) 490 { 491 throw new IOException( this.getMessage( "missingSchemas", new Object[] 492 { 493 getDefaultSchemaLocation() 494 } ) ); 495 496 } 497 498 final JAXBContext context = JAXBContext.newInstance( packageNames.toString().substring( 1 ), classLoader ); 499 final Marshaller m = context.createMarshaller(); 500 501 if ( schemaLocation.length() != 0 ) 502 { 503 m.setProperty( Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation.toString().substring( 1 ) ); 504 } 505 506 return m; 507 } 508 509 public Unmarshaller getUnmarshaller( final ClassLoader classLoader ) throws IOException, SAXException, JAXBException 510 { 511 if ( classLoader == null ) 512 { 513 throw new NullPointerException( "classLoader" ); 514 } 515 516 return this.getContext( classLoader ).createUnmarshaller(); 517 } 518 519 // SECTION-END 520 // SECTION-START[DefaultModelManager] 521 /** Listener interface. */ 522 public interface Listener 523 { 524 525 /** 526 * Get called on logging. 527 * 528 * @param level The level of the event. 529 * @param message The message of the event or {@code null}. 530 * @param t The throwable of the event or {@code null}. 531 * 532 * @throws NullPointerException if {@code level} is {@code null}. 533 */ 534 void onLog( Level level, String message, Throwable t ); 535 536 } 537 538 /** 539 * Log level events are logged at by default. 540 * @see #getDefaultLogLevel() 541 */ 542 private static final Level DEFAULT_LOG_LEVEL = Level.WARNING; 543 544 /** 545 * Classpath location searched for modules by default. 546 * @see #getDefaultModuleLocation() 547 */ 548 private static final String DEFAULT_MODULE_LOCATION = "META-INF/jomc.xml"; 549 550 /** 551 * Classpath location searched for transformers by default. 552 * @see #getDefaultTransformerLocation() 553 */ 554 private static final String DEFAULT_TRANSFORMER_LOCATION = "META-INF/jomc.xslt"; 555 556 /** Classpath location of the bootstrap schema. */ 557 private static final String BOOTSTRAP_SCHEMA_LOCATION = 558 Schemas.class.getPackage().getName().replace( '.', '/' ) + "/jomc-bootstrap-1.0.xsd"; 559 560 /** Value for property {@link Marshaller#JAXB_SCHEMA_LOCATION} of any bootstrap JAXB marshaller instance. */ 561 private static final String BOOTSTRAP_JAXB_SCHEMA_LOCATION = 562 "http://jomc.org/model/bootstrap http://jomc.org/model/bootstrap/jomc-bootstrap-1.0.xsd"; 563 564 /** 565 * Classpath location searched for schemas by default. 566 * @see #getDefaultSchemaLocation() 567 */ 568 private static final String DEFAULT_SCHEMA_LOCATION = "META-INF/jomc-bootstrap.xml"; 569 570 /** JAXB context of the bootstrap schema. */ 571 private static final String BOOTSTRAP_CONTEXT = Schemas.class.getPackage().getName(); 572 573 /** Supported schema name extensions. */ 574 private static final String[] SCHEMA_EXTENSIONS = new String[] 575 { 576 "xsd" 577 }; 578 579 /** Default log level. */ 580 private static volatile Level defaultLogLevel; 581 582 /** Default module location. */ 583 private static volatile String defaultModuleLocation; 584 585 /** Default schema location. */ 586 private static volatile String defaultSchemaLocation; 587 588 /** Default transformer location. */ 589 private static volatile String defaultTransformerLocation; 590 591 /** The listeners of the instance. */ 592 private List<Listener> listeners; 593 594 /** Log level of the instance. */ 595 private Level logLevel; 596 597 /** Creates a new {@code DefaultModelManager} instance. */ 598 public DefaultModelManager() 599 { 600 super(); 601 } 602 603 /** 604 * Gets the list of registered listeners. 605 * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make 606 * to the returned list will be present inside the object. This is why there is no {@code set} method for the 607 * listeners property.</p> 608 * 609 * @return The list of registered listeners. 610 * 611 * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable) 612 */ 613 public List<Listener> getListeners() 614 { 615 if ( this.listeners == null ) 616 { 617 this.listeners = new LinkedList<Listener>(); 618 } 619 620 return this.listeners; 621 } 622 623 /** 624 * Gets the default log level events are logged at. 625 * <p>The default log level is controlled by system property 626 * {@code org.jomc.model.DefaultModelManager.defaultLogLevel} holding the log level to log events at by default. 627 * If that property is not set, the {@code WARNING} default is returned.</p> 628 * 629 * @return The log level events are logged at by default. 630 * 631 * @see #getLogLevel() 632 * @see Level#parse(java.lang.String) 633 */ 634 public static Level getDefaultLogLevel() 635 { 636 if ( defaultLogLevel == null ) 637 { 638 defaultLogLevel = Level.parse( System.getProperty( "org.jomc.model.DefaultModelManager.defaultLogLevel", 639 DEFAULT_LOG_LEVEL.getName() ) ); 640 641 } 642 643 return defaultLogLevel; 644 } 645 646 /** 647 * Sets the default log level events are logged at. 648 * 649 * @param value The new default level events are logged at or {@code null}. 650 * 651 * @see #getDefaultLogLevel() 652 */ 653 public static void setDefaultLogLevel( final Level value ) 654 { 655 defaultLogLevel = value; 656 } 657 658 /** 659 * Gets the log level of the instance. 660 * 661 * @return The log level of the instance. 662 * 663 * @see #getDefaultLogLevel() 664 * @see #setLogLevel(java.util.logging.Level) 665 * @see #isLoggable(java.util.logging.Level) 666 */ 667 public Level getLogLevel() 668 { 669 if ( this.logLevel == null ) 670 { 671 this.logLevel = getDefaultLogLevel(); 672 this.log( Level.CONFIG, this.getMessage( "defaultLogLevelInfo", new Object[] 673 { 674 this.getClass().getCanonicalName(), this.logLevel.getLocalizedName() 675 } ), null ); 676 677 } 678 679 return this.logLevel; 680 } 681 682 /** 683 * Sets the log level of the instance. 684 * 685 * @param value The new log level of the instance or {@code null}. 686 * 687 * @see #getLogLevel() 688 * @see #isLoggable(java.util.logging.Level) 689 */ 690 public void setLogLevel( final Level value ) 691 { 692 this.logLevel = value; 693 } 694 695 /** 696 * Checks if a message at a given level is provided to the listeners of the instance. 697 * 698 * @param level The level to test. 699 * 700 * @return {@code true} if messages at {@code level} are provided to the listeners of the instance; 701 * {@code false} if messages at {@code level} are not provided to the listeners of the instance. 702 * 703 * @throws NullPointerException if {@code level} is {@code null}. 704 * 705 * @see #getLogLevel() 706 * @see #setLogLevel(java.util.logging.Level) 707 */ 708 public boolean isLoggable( final Level level ) 709 { 710 if ( level == null ) 711 { 712 throw new NullPointerException( "level" ); 713 } 714 715 return level.intValue() >= this.getLogLevel().intValue(); 716 } 717 718 /** 719 * Notifies registered listeners. 720 * 721 * @param level The level of the event. 722 * @param message The message of the event or {@code null}. 723 * @param throwable The throwable of the event {@code null}. 724 * 725 * @throws NullPointerException if {@code level} is {@code null}. 726 * 727 * @see #getListeners() 728 * @see #isLoggable(java.util.logging.Level) 729 */ 730 protected void log( final Level level, final String message, final Throwable throwable ) 731 { 732 if ( level == null ) 733 { 734 throw new NullPointerException( "level" ); 735 } 736 737 if ( this.isLoggable( level ) ) 738 { 739 for ( Listener l : this.getListeners() ) 740 { 741 l.onLog( level, message, throwable ); 742 } 743 } 744 } 745 746 /** 747 * Gets a new bootstrap JAXB context instance. 748 * 749 * @return A new bootstrap JAXB context instance. 750 * 751 * @throws JAXBException if creating a new bootstrap JAXB context instance fails. 752 */ 753 public JAXBContext getBootstrapContext() throws JAXBException 754 { 755 return JAXBContext.newInstance( BOOTSTRAP_CONTEXT ); 756 } 757 758 /** 759 * Gets a new bootstrap JAXB marshaller instance. 760 * 761 * @return A new bootstrap JAXB marshaller instance. 762 * 763 * @throws JAXBException if creating a new bootstrap JAXB marshaller instance fails. 764 */ 765 public Marshaller getBootstrapMarshaller() throws JAXBException 766 { 767 final Marshaller m = this.getBootstrapContext().createMarshaller(); 768 m.setProperty( Marshaller.JAXB_SCHEMA_LOCATION, BOOTSTRAP_JAXB_SCHEMA_LOCATION ); 769 return m; 770 } 771 772 /** 773 * Gets a new bootstrap JAXB unmarshaller instance. 774 * 775 * @return A new bootstrap JAXB unmarshaller instance. 776 * 777 * @throws JAXBException if creating a new bootstrap JAXB unmarshaller instance fails. 778 */ 779 public Unmarshaller getBootstrapUnmarshaller() throws JAXBException 780 { 781 return this.getBootstrapContext().createUnmarshaller(); 782 } 783 784 /** 785 * Gets a new bootstrap JAXP schema instance. 786 * 787 * @return A new bootstrap JAXP schema instance. 788 * 789 * @throws SAXException if parsing the bootstrap schema fails. 790 */ 791 public javax.xml.validation.Schema getBootstrapSchema() throws SAXException 792 { 793 final ClassLoader classLoader = this.getClass().getClassLoader(); 794 return SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ).newSchema( 795 classLoader != null 796 ? classLoader.getResource( BOOTSTRAP_SCHEMA_LOCATION ) 797 : ClassLoader.getSystemResource( BOOTSTRAP_SCHEMA_LOCATION ) ); 798 799 } 800 801 /** 802 * Gets the default location searched for schema resources. 803 * <p>The default schema location is controlled by system property 804 * {@code org.jomc.model.DefaultModelManager.defaultSchemaLocation} holding the location to search for schema 805 * resources by default. If that property is not set, the {@code META-INF/jomc-bootstrap.xml} default is returned. 806 * </p> 807 * 808 * @return The location searched for schema resources by default. 809 * 810 * @see #getClasspathSchemas(java.lang.ClassLoader, java.lang.String) 811 */ 812 public static String getDefaultSchemaLocation() 813 { 814 if ( defaultSchemaLocation == null ) 815 { 816 defaultSchemaLocation = System.getProperty( "org.jomc.model.DefaultModelManager.defaultSchemaLocation", 817 DEFAULT_SCHEMA_LOCATION ); 818 819 } 820 821 return defaultSchemaLocation; 822 } 823 824 /** 825 * Sets the default location searched for schema resources. 826 * 827 * @param value The new default location to search for schema resources or {@code null}. 828 * 829 * @see #getDefaultSchemaLocation() 830 */ 831 public static void setDefaultSchemaLocation( final String value ) 832 { 833 defaultSchemaLocation = value; 834 } 835 836 /** 837 * Gets schemas by searching a given class loader for resources. 838 * 839 * @param classLoader The class loader to search for resources. 840 * @param location The location to search at. 841 * 842 * @return All schemas found at {@code location} by querying {@code classLoader}. 843 * 844 * @throws NullPointerException if {@code classLoader} or {@code location} is {@code null}. 845 * @throws IOException if reading resources fails. 846 * @throws SAXException if parsing schema resources fails. 847 * @throws JAXBException if unmarshalling schema resources fails. 848 * 849 * @see #getDefaultSchemaLocation() 850 */ 851 public Schemas getClasspathSchemas( final ClassLoader classLoader, final String location ) 852 throws IOException, JAXBException, SAXException 853 { 854 if ( classLoader == null ) 855 { 856 throw new NullPointerException( "classLoader" ); 857 } 858 if ( location == null ) 859 { 860 throw new NullPointerException( "location" ); 861 } 862 863 final long t0 = System.currentTimeMillis(); 864 final Schemas schemas = new Schemas(); 865 final JAXBContext ctx = JAXBContext.newInstance( BOOTSTRAP_CONTEXT ); 866 final Unmarshaller u = ctx.createUnmarshaller(); 867 final Enumeration<URL> e = classLoader.getResources( location ); 868 u.setSchema( this.getBootstrapSchema() ); 869 int count = 0; 870 871 while ( e.hasMoreElements() ) 872 { 873 count++; 874 final URL url = e.nextElement(); 875 876 if ( this.isLoggable( Level.FINE ) ) 877 { 878 this.log( Level.FINE, this.getMessage( "processing", new Object[] 879 { 880 url.toExternalForm() 881 } ), null ); 882 883 } 884 885 Object content = u.unmarshal( url ); 886 if ( content instanceof JAXBElement ) 887 { 888 content = ( (JAXBElement) content ).getValue(); 889 } 890 891 if ( content instanceof Schema ) 892 { 893 final Schema s = (Schema) content; 894 if ( this.isLoggable( Level.FINE ) ) 895 { 896 this.log( Level.FINE, this.getMessage( "addingSchema", new Object[] 897 { 898 s.getPublicId(), s.getSystemId(), s.getContextId(), s.getClasspathId() 899 } ), null ); 900 901 } 902 903 schemas.getSchema().add( s ); 904 } 905 else if ( content instanceof Schemas ) 906 { 907 for ( Schema s : ( (Schemas) content ).getSchema() ) 908 { 909 if ( this.isLoggable( Level.FINE ) ) 910 { 911 this.log( Level.FINE, this.getMessage( "addingSchema", new Object[] 912 { 913 s.getPublicId(), s.getSystemId(), s.getContextId(), s.getClasspathId() 914 } ), null ); 915 916 } 917 918 schemas.getSchema().add( s ); 919 } 920 } 921 } 922 923 if ( this.isLoggable( Level.FINE ) ) 924 { 925 this.log( Level.FINE, this.getMessage( "classpathReport", new Object[] 926 { 927 count, location, Long.valueOf( System.currentTimeMillis() - t0 ) 928 } ), null ); 929 930 } 931 932 return schemas; 933 } 934 935 /** 936 * Gets the default location searched for module resources. 937 * <p>The default module location is controlled by system property 938 * {@code org.jomc.model.DefaultModelManager.defaultModuleLocation} holding the location to search for module 939 * resources by default. If that property is not set, the {@code META-INF/jomc.xml} default is returned.</p> 940 * 941 * @return The location searched for module resources by default. 942 * 943 * @see #getClasspathModules(java.lang.ClassLoader, java.lang.String) 944 */ 945 public static String getDefaultModuleLocation() 946 { 947 if ( defaultModuleLocation == null ) 948 { 949 defaultModuleLocation = System.getProperty( "org.jomc.model.DefaultModelManager.defaultModuleLocation", 950 DEFAULT_MODULE_LOCATION ); 951 952 } 953 954 return defaultModuleLocation; 955 } 956 957 /** 958 * Sets the default location searched for module resources. 959 * 960 * @param value The new default location to search for module resources or {@code null}. 961 * 962 * @see #getDefaultModuleLocation() 963 */ 964 public static void setDefaultModuleLocation( final String value ) 965 { 966 defaultModuleLocation = value; 967 } 968 969 /** 970 * Gets modules by searching a given class loader for resources. 971 * <p><b>Note:</b><br/> 972 * This method does not validate the modules.</p> 973 * 974 * @param classLoader The class loader to search for resources. 975 * @param location The location to search at. 976 * 977 * @return All modules found at {@code location} by querying {@code classLoader}. 978 * 979 * @throws NullPointerException if {@code classLoader} or {@code location} is {@code null}. 980 * @throws IOException if reading resources fails. 981 * @throws SAXException if parsing schema resources fails. 982 * @throws JAXBException if unmarshalling schema resources fails. 983 * 984 * @see #getDefaultModuleLocation() 985 * @see ModelObjectValidator 986 */ 987 public Modules getClasspathModules( final ClassLoader classLoader, final String location ) 988 throws IOException, SAXException, JAXBException 989 { 990 if ( classLoader == null ) 991 { 992 throw new NullPointerException( "classLoader" ); 993 } 994 if ( location == null ) 995 { 996 throw new NullPointerException( "location" ); 997 } 998 999 final long t0 = System.currentTimeMillis(); 1000 final Text text = new Text(); 1001 text.setLanguage( "en" ); 1002 text.setValue( this.getMessage( "classpathModulesInfo", new Object[] 1003 { 1004 location 1005 } ) ); 1006 1007 final Modules modules = new Modules(); 1008 modules.setDocumentation( new Texts() ); 1009 modules.getDocumentation().setDefaultLanguage( "en" ); 1010 modules.getDocumentation().getText().add( text ); 1011 1012 final Unmarshaller u = this.getUnmarshaller( classLoader ); 1013 final Enumeration<URL> resources = classLoader.getResources( location ); 1014 1015 int count = 0; 1016 while ( resources.hasMoreElements() ) 1017 { 1018 count++; 1019 final URL url = resources.nextElement(); 1020 1021 if ( this.isLoggable( Level.FINE ) ) 1022 { 1023 this.log( Level.FINE, this.getMessage( "processing", new Object[] 1024 { 1025 url.toExternalForm() 1026 } ), null ); 1027 1028 } 1029 1030 Object content = u.unmarshal( url ); 1031 if ( content instanceof JAXBElement ) 1032 { 1033 content = ( (JAXBElement) content ).getValue(); 1034 } 1035 1036 if ( content instanceof Module ) 1037 { 1038 final Module m = (Module) content; 1039 if ( this.isLoggable( Level.FINE ) ) 1040 { 1041 this.log( Level.FINE, this.getMessage( "addingModule", new Object[] 1042 { 1043 m.getName(), m.getVersion() == null ? "" : m.getVersion() 1044 } ), null ); 1045 1046 } 1047 1048 modules.getModule().add( m ); 1049 } 1050 else if ( this.isLoggable( Level.WARNING ) ) 1051 { 1052 this.log( Level.WARNING, this.getMessage( "ignoringDocument", new Object[] 1053 { 1054 content == null ? "<>" : content.toString(), url.toExternalForm() 1055 } ), null ); 1056 1057 } 1058 } 1059 1060 if ( this.isLoggable( Level.FINE ) ) 1061 { 1062 this.log( Level.FINE, this.getMessage( "classpathReport", new Object[] 1063 { 1064 count, location, Long.valueOf( System.currentTimeMillis() - t0 ) 1065 } ), null ); 1066 1067 } 1068 1069 return modules; 1070 } 1071 1072 /** 1073 * Gets the default location searched for transformer resources. 1074 * <p>The default transformer location is controlled by system property 1075 * {@code org.jomc.model.DefaultModelManager.defaultTransformerLocation} holding the location to search for 1076 * transformer resources by default. If that property is not set, the {@code META-INF/jomc.xslt} default is 1077 * returned.</p> 1078 * 1079 * @return The location searched for transformer resources by default. 1080 * 1081 * @see #getClasspathTransformers(java.lang.ClassLoader, java.lang.String) 1082 */ 1083 public static String getDefaultTransformerLocation() 1084 { 1085 if ( defaultTransformerLocation == null ) 1086 { 1087 defaultTransformerLocation = 1088 System.getProperty( "org.jomc.model.DefaultModelManager.defaultTransformerLocation", 1089 DEFAULT_TRANSFORMER_LOCATION ); 1090 1091 } 1092 1093 return defaultTransformerLocation; 1094 } 1095 1096 /** 1097 * Sets the default location searched for transformer resources. 1098 * 1099 * @param value The new default location to search for transformer resources or {@code null}. 1100 * 1101 * @see #getDefaultTransformerLocation() 1102 */ 1103 public static void setDefaultTransformerLocation( final String value ) 1104 { 1105 defaultTransformerLocation = value; 1106 } 1107 1108 /** 1109 * Gets transformers by searching a given class loader for resources. 1110 * 1111 * @param classLoader The class loader to search for resources. 1112 * @param location The location to search at. 1113 * 1114 * @return All transformers found at {@code location} by querying {@code classLoader}. 1115 * 1116 * @throws NullPointerException if {@code classLoader} or {@code location} is {@code null}. 1117 * @throws IOException if reading resources fails. 1118 * @throws TransformerConfigurationException if getting the transformers fails. 1119 * 1120 * @see #getDefaultTransformerLocation() 1121 */ 1122 public List<Transformer> getClasspathTransformers( final ClassLoader classLoader, final String location ) 1123 throws IOException, TransformerConfigurationException 1124 { 1125 if ( classLoader == null ) 1126 { 1127 throw new NullPointerException( "classLoader" ); 1128 } 1129 if ( location == null ) 1130 { 1131 throw new NullPointerException( "location" ); 1132 } 1133 1134 final long t0 = System.currentTimeMillis(); 1135 final List<Transformer> transformers = new LinkedList<Transformer>(); 1136 final TransformerFactory transformerFactory = TransformerFactory.newInstance(); 1137 final Enumeration<URL> resources = classLoader.getResources( location ); 1138 final ErrorListener errorListener = new ErrorListener() 1139 { 1140 1141 public void warning( final TransformerException exception ) throws TransformerException 1142 { 1143 if ( isLoggable( Level.WARNING ) ) 1144 { 1145 log( Level.WARNING, exception.getMessage(), exception ); 1146 } 1147 } 1148 1149 public void error( final TransformerException exception ) throws TransformerException 1150 { 1151 if ( isLoggable( Level.SEVERE ) ) 1152 { 1153 log( Level.SEVERE, exception.getMessage(), exception ); 1154 } 1155 1156 throw exception; 1157 } 1158 1159 public void fatalError( final TransformerException exception ) throws TransformerException 1160 { 1161 if ( isLoggable( Level.SEVERE ) ) 1162 { 1163 log( Level.SEVERE, exception.getMessage(), exception ); 1164 } 1165 1166 throw exception; 1167 } 1168 1169 }; 1170 1171 final URIResolver uriResolver = new URIResolver() 1172 { 1173 1174 public Source resolve( final String href, final String base ) throws TransformerException 1175 { 1176 try 1177 { 1178 final InputSource inputSource = getEntityResolver( classLoader ).resolveEntity( null, href ); 1179 1180 if ( inputSource != null ) 1181 { 1182 return new SAXSource( inputSource ); 1183 } 1184 1185 return null; 1186 } 1187 catch ( final SAXException e ) 1188 { 1189 if ( isLoggable( Level.SEVERE ) ) 1190 { 1191 log( Level.SEVERE, e.getMessage(), e ); 1192 } 1193 1194 throw new TransformerException( e ); 1195 } 1196 catch ( final IOException e ) 1197 { 1198 if ( isLoggable( Level.SEVERE ) ) 1199 { 1200 log( Level.SEVERE, e.getMessage(), e ); 1201 } 1202 1203 throw new TransformerException( e ); 1204 } 1205 } 1206 1207 }; 1208 1209 transformerFactory.setErrorListener( errorListener ); 1210 transformerFactory.setURIResolver( uriResolver ); 1211 1212 int count = 0; 1213 while ( resources.hasMoreElements() ) 1214 { 1215 count++; 1216 final URL url = resources.nextElement(); 1217 1218 if ( this.isLoggable( Level.FINE ) ) 1219 { 1220 this.log( Level.FINE, this.getMessage( "processing", new Object[] 1221 { 1222 url.toExternalForm() 1223 } ), null ); 1224 1225 } 1226 1227 final InputStream in = url.openStream(); 1228 final Transformer transformer = transformerFactory.newTransformer( new StreamSource( in ) ); 1229 in.close(); 1230 1231 transformer.setErrorListener( errorListener ); 1232 transformer.setURIResolver( uriResolver ); 1233 transformers.add( transformer ); 1234 } 1235 1236 if ( this.isLoggable( Level.FINE ) ) 1237 { 1238 this.log( Level.FINE, this.getMessage( "classpathReport", new Object[] 1239 { 1240 count, location, Long.valueOf( System.currentTimeMillis() - t0 ) 1241 } ), null ); 1242 1243 } 1244 1245 return transformers; 1246 } 1247 1248 /** 1249 * Searches all available {@code META-INF/MANIFEST.MF} resources from a given class loader and returns a set 1250 * containing URLs of entries whose name end with a known schema extension. 1251 * 1252 * @param classLoader The class loader to search for resources. 1253 * 1254 * @return URLs of any matching entries. 1255 * 1256 * @throws NullPointerException if {@code classLoader} is {@code null}. 1257 * @throws IOException if reading or parsing fails. 1258 */ 1259 private Set<URL> getSchemaResources( final ClassLoader classLoader ) throws IOException 1260 { 1261 if ( classLoader == null ) 1262 { 1263 throw new NullPointerException( "classLoader" ); 1264 } 1265 1266 final Set<URL> resources = new HashSet<URL>(); 1267 final long t0 = System.currentTimeMillis(); 1268 int count = 0; 1269 1270 for ( final Enumeration<URL> e = classLoader.getResources( "META-INF/MANIFEST.MF" ); e.hasMoreElements(); ) 1271 { 1272 count++; 1273 final URL manifestUrl = e.nextElement(); 1274 final String externalForm = manifestUrl.toExternalForm(); 1275 final String baseUrl = externalForm.substring( 0, externalForm.indexOf( "META-INF" ) ); 1276 final InputStream manifestStream = manifestUrl.openStream(); 1277 final Manifest mf = new Manifest( manifestStream ); 1278 manifestStream.close(); 1279 1280 if ( this.isLoggable( Level.FINE ) ) 1281 { 1282 this.log( Level.FINE, this.getMessage( "processing", new Object[] 1283 { 1284 externalForm 1285 } ), null ); 1286 1287 } 1288 1289 for ( Map.Entry<String, Attributes> entry : mf.getEntries().entrySet() ) 1290 { 1291 for ( int i = SCHEMA_EXTENSIONS.length - 1; i >= 0; i-- ) 1292 { 1293 if ( entry.getKey().toLowerCase().endsWith( '.' + SCHEMA_EXTENSIONS[i].toLowerCase() ) ) 1294 { 1295 final URL schemaUrl = new URL( baseUrl + entry.getKey() ); 1296 resources.add( schemaUrl ); 1297 1298 if ( this.isLoggable( Level.FINE ) ) 1299 { 1300 this.log( Level.FINE, this.getMessage( "addingSchemaCandidate", new Object[] 1301 { 1302 schemaUrl.toExternalForm() 1303 } ), null ); 1304 1305 } 1306 } 1307 } 1308 } 1309 } 1310 1311 if ( this.isLoggable( Level.FINE ) ) 1312 { 1313 this.log( Level.FINE, this.getMessage( "classpathReport", new Object[] 1314 { 1315 count, "META-INF/MANIFEST.MF", Long.valueOf( System.currentTimeMillis() - t0 ) 1316 } ), null ); 1317 1318 } 1319 1320 return resources; 1321 } 1322 1323 private String getMessage( final String key, final Object args ) 1324 { 1325 return new MessageFormat( 1326 ResourceBundle.getBundle( DefaultModelManager.class.getName().replace( '.', '/' ), Locale.getDefault() ). 1327 getString( key ) ).format( args ); 1328 1329 } 1330 1331 // SECTION-END 1332 }