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 }